diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixHandler.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixHandler.java index fd28cf8af..7463c31f1 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixHandler.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2023 Pivotal, 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 @@ -10,7 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.languageserver.quickfix; +import reactor.core.publisher.Mono; + @FunctionalInterface public interface QuickfixHandler { - QuickfixEdit createEdits(Object params); + Mono createEdits(Object params); } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixRegistry.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixRegistry.java index 813baa19d..04f4af8fc 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixRegistry.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixRegistry.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2023 Pivotal, 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 @@ -39,7 +39,7 @@ public class QuickfixRegistry { return new QuickfixType() { @Override - public QuickfixEdit createEdits(Object params) { + public Mono createEdits(Object params) { return handler.createEdits(params); } @@ -55,7 +55,7 @@ public class QuickfixRegistry { return new QuickfixType() { @Override - public QuickfixEdit createEdits(Object params) { + public Mono createEdits(Object params) { return handler.createEdits(params); } @@ -68,9 +68,7 @@ public class QuickfixRegistry { public Mono handle(QuickfixResolveParams params) { QuickfixHandler handler = registry.get(params.getType()); - return Mono.fromSupplier(() -> { - return handler.createEdits(params.getParams()); - }); + return handler.createEdits(params.getParams()); } public boolean hasFixes() { diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/CodeActionResolver.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/CodeActionResolver.java index 876571db4..7dd0a4114 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/CodeActionResolver.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/CodeActionResolver.java @@ -1,9 +1,12 @@ package org.springframework.ide.vscode.commons.languageserver.util; +import java.util.concurrent.CompletableFuture; + import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.WorkspaceEdit; public interface CodeActionResolver { - void resolve(CodeAction codeAction); + CompletableFuture resolve(CodeAction codeAction); } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java index 59473dede..06d0b4ee8 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java @@ -15,7 +15,6 @@ import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -550,18 +549,17 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE @Override public CompletableFuture resolveCodeAction(CodeAction ca) { - return CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> { - if (appContext!=null) { - Map resolvers = appContext.getBeansOfType(CodeActionResolver.class); - for (CodeActionResolver r : resolvers.values()) { - r.resolve(ca); - if (ca.getEdit() != null) { - return ca; - } - } - } - return ca; - }); + return CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> appContext == null ? Collections.emptyMap() : appContext.getBeansOfType(CodeActionResolver.class)) + .thenCompose(resolvers -> { + List> resolutions = resolvers.values().stream().map(r -> r.resolve(ca).thenAccept(we -> { + if (ca.getEdit() != null) { + throw new IllegalStateException("More than one CodeActionResolver resolves the code action"); + } else { + ca.setEdit(we); + } + })).collect(Collectors.toList()); + return CompletableFuture.allOf(resolutions.toArray(new CompletableFuture[resolutions.size()])).thenApply(v -> ca); + }); } @Override diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/quickfix/YamlQuickfixes.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/quickfix/YamlQuickfixes.java index 63f9ad5a0..cb5557531 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/quickfix/YamlQuickfixes.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/quickfix/YamlQuickfixes.java @@ -39,6 +39,8 @@ import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.JsonElement; +import reactor.core.publisher.Mono; + public class YamlQuickfixes { private static final Logger LOG = LoggerFactory.getLogger(YamlQuickfixes.class); @@ -53,7 +55,7 @@ public class YamlQuickfixes { private final Gson gson = new Gson(); public YamlQuickfixes(QuickfixRegistry r, SimpleTextDocumentService textDocumentService, YamlStructureProvider structureProvider) { - MISSING_PROP_FIX = r.register("MISSING_PROP_FIX", (Object _params) -> { + MISSING_PROP_FIX = r.register("MISSING_PROP_FIX", (Object _params) -> Mono.fromSupplier(() -> { MissingPropertiesData params = gson.fromJson((JsonElement)_params, MissingPropertiesData.class); try { TextDocument _doc = textDocumentService.getLatestSnapshot(params.getUri()); @@ -91,9 +93,9 @@ public class YamlQuickfixes { } //Something went wrong. Return empty edit object. return NULL_FIX; - }); + })); - SIMPLE_TEXT_EDIT = r.register("SIMPLE_TEXT_EDIT", (_params) -> { + SIMPLE_TEXT_EDIT = r.register("SIMPLE_TEXT_EDIT", (_params) -> Mono.fromSupplier(() -> { try { ReplaceStringData params = gson.fromJson((JsonElement)_params, ReplaceStringData.class); TextDocument _doc = textDocumentService.getLatestSnapshot(params.getUri()); @@ -111,7 +113,7 @@ public class YamlQuickfixes { } //Something went wrong. Return empty edit object. return NULL_FIX; - }); + })); } public static QuickfixEdit createReplacementQuickfic(TextDocument doc, YamlPathEdits edits) throws BadLocationException { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java index c9c99edf8..93fcc220d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java @@ -32,7 +32,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -46,7 +45,6 @@ import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDisc; import org.springframework.ide.vscode.boot.index.cache.IndexCacheVoid; import org.springframework.ide.vscode.boot.java.JavaDefinitionHandler; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider; -import org.springframework.ide.vscode.boot.java.handlers.BootJavaProjectReconcilerScheduler; import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine; import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler; import org.springframework.ide.vscode.boot.java.links.DefaultJavaElementLocationProvider; @@ -64,7 +62,6 @@ import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDa import org.springframework.ide.vscode.boot.java.reconcilers.JavaReconciler; import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler; import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler; -import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; import org.springframework.ide.vscode.boot.java.value.PropertyValueAnnotationDefProvider; import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsService; @@ -352,8 +349,8 @@ public class BootLanguageServerBootApp { } @Bean - BootJavaReconcileEngine getBootJavaReconcileEngine(JavaProjectFinder projectFinder, JavaReconciler[] javaReconcilers, SimpleLanguageServer server) { - return new BootJavaReconcileEngine(projectFinder, javaReconcilers, server); + BootJavaReconcileEngine getBootJavaReconcileEngine(JavaProjectFinder projectFinder, JavaReconciler[] javaReconcilers) { + return new BootJavaReconcileEngine(projectFinder, javaReconcilers); } @Bean @@ -361,16 +358,6 @@ public class BootLanguageServerBootApp { return new BootJavaCodeActionProvider(projectFinder, codeActionHandlers); } - @ConditionalOnMissingClass("org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness") - @ConditionalOnProperty(prefix = "languageserver", name = "reconcile-only-opened-docs", havingValue = "false", matchIfMissing = true) - @Bean - BootJavaProjectReconcilerScheduler bootJavaProjectReconcilerScheduler(SimpleLanguageServer server, - BootJavaReconcileEngine bootJavaReconciler, ProjectObserver projectObserver, BootJavaConfig config, - Optional recipeRepoOpt, JavaProjectFinder projectFinder) { - return new BootJavaProjectReconcilerScheduler(bootJavaReconciler, projectObserver, config, - recipeRepoOpt.orElse(null), projectFinder, server); - } - @Bean JavaDefinitionHandler javaDefinitionHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder) { return new JavaDefinitionHandler(cuCache, projectFinder, List.of(new PropertyValueAnnotationDefProvider())); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java index cbc390c9a..fa615d86c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java @@ -28,9 +28,7 @@ import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider; import org.springframework.ide.vscode.boot.java.links.SourceLinks; import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDataProvider; -import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; -import org.springframework.ide.vscode.boot.java.utils.ServerUtils; import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents; import org.springframework.ide.vscode.boot.xml.SpringXMLLanguageServerComponents; @@ -73,7 +71,6 @@ public class BootLanguageServerInitializer implements InitializingBean { @Autowired SpringSymbolIndex springIndexer; @Autowired(required = false) List completionEngines; @Autowired private JavaProjectFinder projectFinder; - @Autowired(required = false) private RewriteRecipeRepository recipesRepo; @Qualifier("adHocProperties") @Autowired ProjectBasedPropertyIndexProvider adHocProperties; @@ -148,24 +145,9 @@ public class BootLanguageServerInitializer implements InitializingBean { components.getCodeActionProvider().ifPresent(documents::onCodeAction); components.getDocumentSymbolProvider().ifPresent(documents::onDocumentSymbol); - - // TODO: seems to hang tests if done on server initialize. Test Harness Server is likely to be initialized already by this point -// server.doOnInitialized(() -> { - 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(); - reconcile(); - }); - } else { - // Reconcile would occur as listeners will be receiving events - startListeningToPerformReconcile(); - //Uncomment reconcile() call if done within server.doOnInitialized() -// reconcile(); - } -// }); - + + startListeningToPerformReconcile(); + server.onCommand("sts/show/document", p -> { ShowDocumentParams showDocParams = new Gson().fromJson((JsonElement)p.getArguments().get(0), ShowDocumentParams.class); return server.getClient().showDocument(showDocParams).thenApply(r -> { @@ -193,7 +175,7 @@ public class BootLanguageServerInitializer implements InitializingBean { server.validateWith(doc.getId(), reconcileEngine); }); - ServerUtils.listenToClassFileChanges(server.getWorkspaceService().getFileObserver(), projectFinder, project -> validateAll(components, server, project)); +// ServerUtils.listenToClassFileChanges(server.getWorkspaceService().getFileObserver(), projectFinder, project -> validateAll(components, server, project)); }); config.addListener(evt -> reconcile()); params.projectObserver.addListener(reconcileDocumentsForProjectChange(server, components, params.projectFinder)); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaProjectReconcilerScheduler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaProjectReconcilerScheduler.java deleted file mode 100644 index 503be019e..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaProjectReconcilerScheduler.java +++ /dev/null @@ -1,89 +0,0 @@ -/******************************************************************************* - * 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 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.rewrite.RewriteRecipeRepository; -import org.springframework.ide.vscode.boot.java.utils.ServerUtils; -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.SimpleLanguageServer; - -public class BootJavaProjectReconcilerScheduler extends ProjectReconcileScheduler { - - private static final Logger log = LoggerFactory.getLogger(BootJavaProjectReconcilerScheduler.class); - - private ProjectObserver projectObserver; - private BootJavaConfig config; - private RewriteRecipeRepository recipesRepo; - - public BootJavaProjectReconcilerScheduler(IJavaProjectReconcileEngine reconciler, ProjectObserver projectObserver, - BootJavaConfig config, RewriteRecipeRepository recipesRepo, JavaProjectFinder projectFinder, SimpleLanguageServer server) { - super(server, reconciler, projectFinder); - this.projectObserver = projectObserver; - this.config = config; - this.recipesRepo = recipesRepo; - } - - @Override - protected void init() { - log.info("Starting project reconciler for Java sources"); - 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(); - log.info("Started project reconciler for Java sources"); - }); - } else { - startListeningToPerformReconcile(); - scheduleValidationForAllProjects(); - log.info("Started project reconciler for Java sources"); - } - } - - 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); - } - }); - - ServerUtils.listenToClassFileChanges(getServer().getWorkspaceService().getFileObserver(), getProjectFinder(), this::scheduleValidation); - - // 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()); - } - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java index c97c19816..aa33a4b83 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java @@ -10,54 +10,32 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.handlers; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -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.common.IJavaProjectReconcileEngine; import org.springframework.ide.vscode.boot.java.reconcilers.JavaReconciler; -import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.PercentageProgressTask; -import org.springframework.ide.vscode.commons.languageserver.ProgressService; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; 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.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; -import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert */ -public class BootJavaReconcileEngine implements IReconcileEngine, IJavaProjectReconcileEngine { +public class BootJavaReconcileEngine implements IReconcileEngine { private static final Logger log = LoggerFactory.getLogger(BootJavaReconcileEngine.class); private final JavaProjectFinder projectFinder; private final JavaReconciler[] javaReconcilers; - private final SimpleLanguageServer server; - public BootJavaReconcileEngine(JavaProjectFinder projectFinder, JavaReconciler[] javaReconcilers, - SimpleLanguageServer server) { + public BootJavaReconcileEngine(JavaProjectFinder projectFinder, JavaReconciler[] javaReconcilers) { this.projectFinder = projectFinder; this.javaReconcilers = javaReconcilers; - this.server = server; } @Override @@ -116,69 +94,4 @@ public class BootJavaReconcileEngine implements IReconcileEngine, IJavaProjectRe } } - @Override - public void reconcile(IJavaProject project, ProgressService progressService) { -// Stream files = IClasspathUtil.getProjectJavaSourceFolders(project.getClasspath()).flatMap(folder -> { -// try { -// return Files.walk(folder.toPath()).filter(Files::isRegularFile); -// } catch (IOException e) { -// return Stream.empty(); -// } -// }); -// -// Stream docIds = files -// .filter(f -> f.getFileName().toString().endsWith(".java")) -// .map(f -> new TextDocumentIdentifier(f.toUri().toASCIIString())); -// -// List docs = docIds.filter(docId -> server.getTextDocumentService().getLatestSnapshot(docId.getUri()) == null) -// .map(docId -> new LazyTextDocument(docId.getUri(), LanguageId.JAVA)).collect(Collectors.toList()); -// -// Map problemCollectors = docs.stream() -// .collect(Collectors.toMap(doc -> doc, doc -> server.createProblemCollector(new AtomicReference<>(doc), null))); -// -// problemCollectors.values().forEach(c -> c.beginCollecting()); -// -// int totalWork = 0; -// for (JavaReconciler jr : javaReconcilers) { -// totalWork += jr.getTotalWorkUnits(docs); -// } -// -// PercentageProgressTask progressTask = progressService.createPercentageProgressTask( -// "reconcile-java-" + project.getElementName(), -// totalWork, -// "Spring Tools: Reconciling Java Sources for '" + project.getElementName() + "'" -// ); -// -// for (JavaReconciler jr : javaReconcilers) { -// try { -// Map> problems = jr.reconcile(project, docs, () -> progressTask.increment()); -// problems.entrySet().forEach(e -> { -// IProblemCollector collector = problemCollectors.get(e.getKey()); -// e.getValue().forEach(p -> collector.accept(p)); -// }); -// } catch (Exception e) { -// log.error("", e); -// } -// } -// -// progressTask.done(); -// problemCollectors.values().forEach(c -> c.endCollecting()); - - } - - @Override - public void clear(IJavaProject project) { -// IClasspathUtil.getProjectJavaSourceFolders(project.getClasspath()).flatMap(folder -> { -// try { -// return Files.walk(folder.toPath()).filter(Files::isRegularFile); -// } catch (IOException e) { -// return Stream.empty(); -// } -// }) -// .filter(f -> f.getFileName().toString().endsWith(".java")) -// .filter(f -> server.getTextDocumentService().getLatestSnapshot(UriUtil.toUri(f.toFile()).toASCIIString()) == null) -// .forEach(p -> server.getTextDocumentService().publishDiagnostics(new TextDocumentIdentifier(p.toUri().toASCIIString()), Collections.emptyList())); - - } - } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java index af5bc3c53..c93642d00 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java @@ -17,7 +17,6 @@ import java.net.URLClassLoader; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -36,27 +35,21 @@ import java.util.stream.Collectors; import org.eclipse.lsp4j.ApplyWorkspaceEditParams; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceEdit; -import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Parser; import org.openrewrite.Recipe; import org.openrewrite.RecipeRun; import org.openrewrite.Result; import org.openrewrite.SourceFile; -import org.openrewrite.TreeVisitor; import org.openrewrite.Validated; import org.openrewrite.config.DeclarativeRecipe; import org.openrewrite.config.RecipeDescriptor; import org.openrewrite.config.YamlResourceLoader; import org.openrewrite.internal.InMemoryLargeSourceSet; import org.openrewrite.java.JavaParser; -import org.openrewrite.java.tree.J.CompilationUnit; import org.openrewrite.maven.MavenParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.ide.vscode.boot.app.BootJavaConfig; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.IndefiniteProgressTask; @@ -67,8 +60,6 @@ import org.springframework.ide.vscode.commons.protocol.java.ProjectBuild; import org.springframework.ide.vscode.commons.rewrite.LoadUtils; import org.springframework.ide.vscode.commons.rewrite.LoadUtils.DurationTypeConverter; import org.springframework.ide.vscode.commons.rewrite.ORDocUtils; -import org.springframework.ide.vscode.commons.rewrite.config.DefaultMarkerVisitorContext; -import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; import org.springframework.ide.vscode.commons.rewrite.config.StsEnvironment; import org.springframework.ide.vscode.commons.rewrite.gradle.GradleIJavaProjectParser; import org.springframework.ide.vscode.commons.rewrite.java.ProjectParser; @@ -79,7 +70,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; -public class RewriteRecipeRepository implements ApplicationContextAware { +public class RewriteRecipeRepository { enum RecipeFilter { ALL, @@ -111,17 +102,11 @@ public class RewriteRecipeRepository implements ApplicationContextAware { final private SimpleLanguageServer server; - final private Map recipes; - final private JavaProjectFinder projectFinder; - final private List codeActionDescriptors; - final private ListenerList loadListeners; - private ApplicationContext applicationContext; - - CompletableFuture loaded; + private CompletableFuture> recipesFuture = null; private Set scanFiles; private Set scanDirs; @@ -134,17 +119,13 @@ public class RewriteRecipeRepository implements ApplicationContextAware { public RewriteRecipeRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder, BootJavaConfig config) { this.server = server; this.projectFinder = projectFinder; - this.recipes = new HashMap<>(); - this.codeActionDescriptors = new ArrayList<>(); this.loadListeners = new ListenerList<>(); this.scanDirs = UNINITIALIZED_SET; this.scanFiles = UNINITIALIZED_SET; this.recipeFilters = UNINITIALIZED_SET; - CompletableFuture firstConfigLoaded = new CompletableFuture<>(); config.addListener(l -> { Set recipeFilterFromConfig = config.getRecipesFilters(); - boolean firstTimeConfig = recipeFilters == UNINITIALIZED_SET && scanDirs == UNINITIALIZED_SET && scanFiles == UNINITIALIZED_SET; if (recipeFilters == UNINITIALIZED_SET || !recipeFilters.equals(recipeFilterFromConfig)) { recipeFilters = recipeFilterFromConfig; } @@ -154,38 +135,20 @@ public class RewriteRecipeRepository implements ApplicationContextAware { // Therefore it is best to store the scanDirs here right after it is received, not during scan process or anything else done async scanDirs = config.getRecipeDirectories(); scanFiles = config.getRecipeFiles(); - if (!firstTimeConfig) { - load(); - } - } - // First time config loaded. Received when server fully initialized. Start load after the firstConfigLoaded then and assign it to loaded field such that it is never null - if (firstTimeConfig) { - firstConfigLoaded.complete(null); + clearRecipes(); } }); - // Initial configuration is followed by load() as a special case to have 'loaded' future value to never be null - this.loaded = firstConfigLoaded.thenCompose(v -> load()); - registerCommands(); } - - - public CompletableFuture load() { - return this.loaded = CompletableFuture.runAsync(() -> { - clearRecipes(); - loadRecipes(); - loadListeners.fire(null); - }); - } - + private synchronized void clearRecipes() { - recipes.clear(); - codeActionDescriptors.clear(); + recipesFuture = null; } - private synchronized void loadRecipes() { + private synchronized Map loadRecipes() { IndefiniteProgressTask progressTask = server.getProgressService().createIndefiniteProgressTask(UUID.randomUUID().toString(), "Loading Rewrite Recipes", null); + Map recipes = new HashMap<>(); try { log.info("Loading Rewrite Recipes..."); Recipe xmlbindRecipe = null; @@ -211,13 +174,14 @@ public class RewriteRecipeRepository implements ApplicationContextAware { } } } - codeActionDescriptors.addAll(env.listCodeActionDescriptors()); +// codeActionDescriptors.addAll(env.listCodeActionDescriptors()); log.info("Done loading Rewrite Recipes"); } catch (Throwable t) { log.error("", t); } finally { progressTask.done(); } + return recipes; } private boolean isAcceptableGlobalCommandRecipe(Recipe r) { @@ -285,52 +249,15 @@ public class RewriteRecipeRepository implements ApplicationContextAware { return builder.build(); } - public Optional getRecipe(String name) { - return Optional.ofNullable(recipes.get(name)); + public CompletableFuture> recipes() { + if (recipesFuture == null) { + recipesFuture = CompletableFuture.supplyAsync(this::loadRecipes); + } + return recipesFuture; } - public RecipeCodeActionDescriptor getCodeActionRecipeDescriptor(String id) { - for (RecipeCodeActionDescriptor d : codeActionDescriptors) { - if (id.equals(d.getId())) { - return d; - } - } - return null; - } - - public List getProblemRecipeDescriptors() { - List l = new ArrayList<>(codeActionDescriptors.size()); - for (RecipeCodeActionDescriptor d : codeActionDescriptors) { - if (d.getProblemType() != null && server.getDiagnosticSeverityProvider().getDiagnosticSeverity(d.getProblemType()) != null) { - l.add(d); - } - } - return l; - } - - public List getCodeActionRecipeDescriptors() { - List l = new ArrayList<>(codeActionDescriptors.size()); - for (RecipeCodeActionDescriptor d : codeActionDescriptors) { - if (d.getProblemType() == null || server.getDiagnosticSeverityProvider().getDiagnosticSeverity(d.getProblemType()) == null) { - l.add(d); - } - } - return l; - } - - public CompilationUnit mark(IJavaProject project, List descriptors, CompilationUnit compilationUnit) { - CompilationUnit cu = compilationUnit; - for (RecipeCodeActionDescriptor d : descriptors) { - TreeVisitor markVisitor = d.getMarkerVisitor(new DefaultMarkerVisitorContext(applicationContext, project)); - if (markVisitor != null) { - try { - cu = (CompilationUnit) markVisitor.visit(cu, new InMemoryExecutionContext(e -> log.error("Marker visitor failed!", e))); - } catch (Exception e) { - // ignore - would happen in sources with compiler errors - } - } - } - return cu; + public CompletableFuture> getRecipe(String name) { + return recipes().thenApply(recipes -> Optional.ofNullable(recipes.get(name))); } private static JsonElement recipeToJson(Recipe r) { @@ -342,26 +269,20 @@ public class RewriteRecipeRepository implements ApplicationContextAware { server.onCommand(CMD_REWRITE_LIST, params -> { JsonElement uri = (JsonElement) params.getArguments().get(0); RecipeFilter f = params.getArguments().size() > 1 ? RecipeFilter.valueOf(((JsonElement) params.getArguments().get(1)).getAsString()) : RecipeFilter.ALL; - return loaded.thenApply(v -> { - if (uri == null) { - return Collections.emptyList(); - } else { - return listProjectRefactoringRecipes(uri.getAsString()).stream() + return listProjectRefactoringRecipes(uri.getAsString()).thenApply(recipes -> recipes.stream() .filter(RECIPE_LIST_FILTERS.get(f)) .map(RewriteRecipeRepository::recipeToJson) - .collect(Collectors.toList()); - } - }); + .collect(Collectors.toList())); }); server.onCommand(CMD_REWRITE_EXECUTE, params -> { - return loaded.thenCompose(v -> { + return recipes().thenCompose(recipes -> { String uri = ((JsonElement) params.getArguments().get(0)).getAsString(); JsonElement recipesJson = ((JsonElement) params.getArguments().get(1)); RecipeDescriptor d = serializationGson.fromJson(recipesJson, RecipeDescriptor.class); - Recipe aggregateRecipe = LoadUtils.createRecipe(d, id -> getRecipe(id).map(r -> r.getClass()).orElse(null)); + Recipe aggregateRecipe = LoadUtils.createRecipe(d, id -> Optional.ofNullable(recipes.get(id)).map(r -> r.getClass()).orElse(null)); if (aggregateRecipe instanceof DeclarativeRecipe && aggregateRecipe.getRecipeList().isEmpty()) { throw new RuntimeException("No recipes found to perform!"); @@ -383,14 +304,19 @@ public class RewriteRecipeRepository implements ApplicationContextAware { }); }); - server.onCommand(CMD_REWRITE_RELOAD, params -> load().thenApply((v) -> "executed")); + server.onCommand(CMD_REWRITE_RELOAD, params -> { + clearRecipes(); + return CompletableFuture.completedFuture("executed"); + }); server.onCommand(CMD_REWRITE_RECIPE_EXECUTE, params -> { String recipeId = ((JsonElement) params.getArguments().get(0)).getAsString(); - Recipe r = getRecipe(recipeId).orElseThrow(() -> new IllegalArgumentException("No such recipe exists with name " + recipeId)); - final String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? r.getName() : params.getWorkDoneToken().getLeft(); - String uri = ((JsonElement) params.getArguments().get(1)).getAsString(); - return apply(r, uri, progressToken); + return getRecipe(recipeId).thenCompose(optRecipe -> { + Recipe r = optRecipe.orElseThrow(() -> new IllegalArgumentException("No such recipe exists with name " + recipeId)); + final String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? r.getName() : params.getWorkDoneToken().getLeft(); + String uri = ((JsonElement) params.getArguments().get(1)).getAsString(); + return apply(r, uri, progressToken); + }); }); } @@ -447,7 +373,7 @@ public class RewriteRecipeRepository implements ApplicationContextAware { return ORDocUtils.createWorkspaceEdit(absoluteProjectDir, server.getTextDocumentService(), results); } - private List listProjectRefactoringRecipes(String uri) { + private CompletableFuture> listProjectRefactoringRecipes(String uri) { if (uri != null) { /* * When LS started on listing rewrite recipes project lookup may not find any projects as classpath might still be resolving. @@ -456,10 +382,10 @@ public class RewriteRecipeRepository implements ApplicationContextAware { */ // Optional projectOpt = projectFinder.find(new TextDocumentIdentifier(uri)); // if (projectOpt.isPresent()) { - return recipes.values().stream().filter(this::isAcceptableGlobalCommandRecipe).collect(Collectors.toList()); + return recipes().thenApply(recipes -> recipes.values().stream().filter(this::isAcceptableGlobalCommandRecipe).collect(Collectors.toList())); // } } - return Collections.emptyList(); + return CompletableFuture.completedFuture(Collections.emptyList()); } private static ProjectParser createRewriteProjectParser(IJavaProject jp, Function inputProvider) { @@ -480,11 +406,6 @@ public class RewriteRecipeRepository implements ApplicationContextAware { loadListeners.add(l); } - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - private static String lastTokenAfterDot(String s) { int idx = s.lastIndexOf('.'); if (idx >= 0 && idx < s.length() - 1) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java index e1c55f6c6..ee912fa9e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java @@ -105,75 +105,77 @@ public class RewriteReconciler implements JavaReconciler { // } } - private List createProblems(IDocument doc, FixAssistMarker m, J astNode) { - if (astNode != null) { - Range range = astNode.getMarkers().findFirst(Range.class).orElse(null); - if (range != null) { - RecipeCodeActionDescriptor recipeFixDescriptor = recipeRepo.getCodeActionRecipeDescriptor(m.getDescriptorId()); - if (recipeFixDescriptor != null) { - return List.of(createProblem(doc, recipeFixDescriptor, m, range)); - } - } - } - return Collections.emptyList(); - } - - private ReconcileProblemImpl createProblem(IDocument doc, RecipeCodeActionDescriptor recipeFixDescriptor, - FixAssistMarker m, Range range) { - ProblemType problemType = recipeFixDescriptor.getProblemType(); - ReconcileProblemImpl problem = new ReconcileProblemImpl(problemType, m.getLabel() == null ? problemType.getLabel() : m.getLabel(), range.getStart().getOffset(), range.getEnd().getOffset() - range.getStart().getOffset()); - QuickfixType quickfixType = quickfixRegistry.getQuickfixType(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX); - if (quickfixType != null) { - for (FixDescriptor f : m.getFixes()) { - if (recipeRepo.getRecipe(f.getRecipeId()).isPresent()) { - problem.addQuickfix(new QuickfixData<>( - quickfixType, - f, - f.getLabel() - )); - } - } - } - return problem; - } +// private List createProblems(IDocument doc, FixAssistMarker m, J astNode) { +// if (astNode != null) { +// Range range = astNode.getMarkers().findFirst(Range.class).orElse(null); +// if (range != null) { +// RecipeCodeActionDescriptor recipeFixDescriptor = recipeRepo.getCodeActionRecipeDescriptor(m.getDescriptorId()); +// if (recipeFixDescriptor != null) { +// return List.of(createProblem(doc, recipeFixDescriptor, m, range)); +// } +// } +// } +// return Collections.emptyList(); +// } +// +// private ReconcileProblemImpl createProblem(IDocument doc, RecipeCodeActionDescriptor recipeFixDescriptor, +// FixAssistMarker m, Range range) { +// ProblemType problemType = recipeFixDescriptor.getProblemType(); +// ReconcileProblemImpl problem = new ReconcileProblemImpl(problemType, m.getLabel() == null ? problemType.getLabel() : m.getLabel(), range.getStart().getOffset(), range.getEnd().getOffset() - range.getStart().getOffset()); +// QuickfixType quickfixType = quickfixRegistry.getQuickfixType(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX); +// if (quickfixType != null) { +// for (FixDescriptor f : m.getFixes()) { +// if (recipeRepo.getRecipe(f.getRecipeId()).isPresent()) { +// problem.addQuickfix(new QuickfixData<>( +// quickfixType, +// f, +// f.getLabel() +// )); +// } +// } +// } +// return problem; +// } @Override public Map> reconcile(IJavaProject project, List docs, Runnable incrementProgress) { - if (!config.isJavaSourceReconcileEnabled()) { - return Collections.emptyMap(); - } +// if (!config.isJavaSourceReconcileEnabled()) { +// return Collections.emptyMap(); +// } +// +// long start = System.currentTimeMillis(); +// +// Map> allProblems = new HashMap<>(); +// List testSourceFolders = IClasspathUtil.getProjectTestJavaSources(project.getClasspath()).map(f -> f.toPath()).collect(Collectors.toList()); +// List testSources = new ArrayList<>(docs.size()); +// List mainSources = new ArrayList<>(docs.size()); +// for (TextDocument d : docs) { +// Path p = Paths.get(URI.create(d.getUri())); +// if (testSourceFolders.stream().anyMatch(t -> p.startsWith(t))) { +// testSources.add(d); +// } else { +// mainSources.add(d); +// } +// } +// +// List classpath = IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath()).collect(Collectors.toList()); +// JavaParser javaParser = ORAstUtils.createJavaParser(() -> JavaParser.fromJavaVersion().classpath(classpath)); +// +// // Pass in source sets created from classpath. (Perhaps it is a good idea to have separate classpath and parsers for test and main, TBD) +// // Perhaps it is even better to create empty classpath java source sets as reconcile step seem to only need name of the java source set +// // Usually java source set classpath is required to figure out how to organize imports for sources +// JavaSourceSet mainJavaSourceSet = JavaSourceSet.build(ProjectParser.MAIN, classpath, null, false); +// JavaSourceSet testJavaSourceSet = new JavaSourceSet(Tree.randomId(), ProjectParser.TEST, mainJavaSourceSet.getClasspath()); +// allProblems.putAll(doReconcile(project, mainSources, javaParser, mainJavaSourceSet, incrementProgress)); +// allProblems.putAll(doReconcile(project, testSources, javaParser, testJavaSourceSet, incrementProgress)); +// +// long end = System.currentTimeMillis(); +// log.info("reconciling project (OpenRewrite): " + project.getElementName() + " - " + docs.size() + " done in " + (end - start) + "ms"); +// +// return allProblems; - long start = System.currentTimeMillis(); - - Map> allProblems = new HashMap<>(); - List testSourceFolders = IClasspathUtil.getProjectTestJavaSources(project.getClasspath()).map(f -> f.toPath()).collect(Collectors.toList()); - List testSources = new ArrayList<>(docs.size()); - List mainSources = new ArrayList<>(docs.size()); - for (TextDocument d : docs) { - Path p = Paths.get(URI.create(d.getUri())); - if (testSourceFolders.stream().anyMatch(t -> p.startsWith(t))) { - testSources.add(d); - } else { - mainSources.add(d); - } - } - - List classpath = IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath()).collect(Collectors.toList()); - JavaParser javaParser = ORAstUtils.createJavaParser(() -> JavaParser.fromJavaVersion().classpath(classpath)); - - // Pass in source sets created from classpath. (Perhaps it is a good idea to have separate classpath and parsers for test and main, TBD) - // Perhaps it is even better to create empty classpath java source sets as reconcile step seem to only need name of the java source set - // Usually java source set classpath is required to figure out how to organize imports for sources - JavaSourceSet mainJavaSourceSet = JavaSourceSet.build(ProjectParser.MAIN, classpath, null, false); - JavaSourceSet testJavaSourceSet = new JavaSourceSet(Tree.randomId(), ProjectParser.TEST, mainJavaSourceSet.getClasspath()); - allProblems.putAll(doReconcile(project, mainSources, javaParser, mainJavaSourceSet, incrementProgress)); - allProblems.putAll(doReconcile(project, testSources, javaParser, testJavaSourceSet, incrementProgress)); - - long end = System.currentTimeMillis(); - log.info("reconciling project (OpenRewrite): " + project.getElementName() + " - " + docs.size() + " done in " + (end - start) + "ms"); - - return allProblems; + return Collections.emptyMap(); } @@ -249,99 +251,99 @@ public class RewriteReconciler implements JavaReconciler { private static final int BATCH = 50; // Parse in batches and share the parser - private Map> doReconcile(IJavaProject project, List docs, JavaParser javaParser, JavaSourceSet javaSourceSet, Runnable incrementProgress) { - Map> allProblems = new HashMap<>(); - if (javaParser != null && config.isJavaSourceReconcileEnabled()) { - try { - List descriptors = getProblemRecipeDescriptors(project); - - - if (!descriptors.isEmpty()) { - - for (int i = 0; i < docs.size(); i += BATCH) { - List batchList = docs.subList(i, Math.min(i + BATCH, docs.size())); - - List cus = ORAstUtils.parseInputs(javaParser, - batchList.stream().map(d -> new Parser.Input(Paths.get(URI.create(d.getUri())), () -> { - return new ByteArrayInputStream(d.get().getBytes()); - })).collect(Collectors.toList()), source -> incrementProgress.run()); - - cus = ListUtils.map(cus, cu -> cu.withMarkers(cu.getMarkers().computeByType(javaSourceSet, (original, updated) -> updated))); - - /* - * If exception occurs during parsing inputs the list of inputs would become shorter than the list of corresponding documents - */ - - for (int j = 0, k = 0; j < batchList.size() && k < cus.size(); j++) { - final IDocument doc = batchList.get(j); - List problems = new ArrayList<>(); - CompilationUnit cu = cus.get(k); - Path sourcePath = Paths.get(URI.create(doc.getUri())); - if (cu.getSourcePath().equals(sourcePath)) { - k++; - collectProblems(project, descriptors, doc, cu, problems::add); - if (!problems.isEmpty()) { - allProblems.put(doc, problems); - } - } else { - log.warn("(OpenRewrite) Failed to parse source for " + sourcePath); - } - incrementProgress.run(); - } - - } - } - } catch (Exception e) { - if (ORAstUtils.isExceptionFromInterrupedThread(e)) { - log.debug("", e); - } else { - log.error("", e); - } - } - } - return allProblems; - } - - private List getProblemRecipeDescriptors(IJavaProject project) - throws InterruptedException, ExecutionException { - return recipeRepo.getProblemRecipeDescriptors().stream().filter(d -> d.getProblemType() != null).filter(d -> { - switch (config.getProblemApplicability(d.getProblemType())) { - case ON: - return SpringProjectUtil.isBootProject(project); - case OFF: - return false; - default: // AUTO - return d.isApplicable(project); - } - }).collect(Collectors.toList()); - } - - private void collectProblems(IJavaProject project, List descriptors, IDocument doc, CompilationUnit compilationUnit, Consumer problemHandler) { - CompilationUnit cu = recipeRepo.mark(project, descriptors, compilationUnit); - if (compilationUnit != cu) { - new JavaMarkerVisitor() { - - @Override - public J visit(Tree tree, ExecutionContext context) { - J t = super.visit(tree, context); - if (t instanceof J) { - for (Marker m : t.getMarkers().entries()) { - if (m instanceof FixAssistMarker) { - for (ReconcileProblem problem : createProblems(doc, (FixAssistMarker) m, t)) { - problemHandler.accept(problem); - } - } - } - } - return t; - } - - }.visit(cu, new InMemoryExecutionContext(e -> log.error("", e))); - } - } - - public int getTotalWorkUnits(List docs) { - return docs.size() * 2; - } +// private Map> doReconcile(IJavaProject project, List docs, JavaParser javaParser, JavaSourceSet javaSourceSet, Runnable incrementProgress) { +// Map> allProblems = new HashMap<>(); +// if (javaParser != null && config.isJavaSourceReconcileEnabled()) { +// try { +// List descriptors = getProblemRecipeDescriptors(project); +// +// +// if (!descriptors.isEmpty()) { +// +// for (int i = 0; i < docs.size(); i += BATCH) { +// List batchList = docs.subList(i, Math.min(i + BATCH, docs.size())); +// +// List cus = ORAstUtils.parseInputs(javaParser, +// batchList.stream().map(d -> new Parser.Input(Paths.get(URI.create(d.getUri())), () -> { +// return new ByteArrayInputStream(d.get().getBytes()); +// })).collect(Collectors.toList()), source -> incrementProgress.run()); +// +// cus = ListUtils.map(cus, cu -> cu.withMarkers(cu.getMarkers().computeByType(javaSourceSet, (original, updated) -> updated))); +// +// /* +// * If exception occurs during parsing inputs the list of inputs would become shorter than the list of corresponding documents +// */ +// +// for (int j = 0, k = 0; j < batchList.size() && k < cus.size(); j++) { +// final IDocument doc = batchList.get(j); +// List problems = new ArrayList<>(); +// CompilationUnit cu = cus.get(k); +// Path sourcePath = Paths.get(URI.create(doc.getUri())); +// if (cu.getSourcePath().equals(sourcePath)) { +// k++; +// collectProblems(project, descriptors, doc, cu, problems::add); +// if (!problems.isEmpty()) { +// allProblems.put(doc, problems); +// } +// } else { +// log.warn("(OpenRewrite) Failed to parse source for " + sourcePath); +// } +// incrementProgress.run(); +// } +// +// } +// } +// } catch (Exception e) { +// if (ORAstUtils.isExceptionFromInterrupedThread(e)) { +// log.debug("", e); +// } else { +// log.error("", e); +// } +// } +// } +// return allProblems; +// } +// +// private List getProblemRecipeDescriptors(IJavaProject project) +// throws InterruptedException, ExecutionException { +// return recipeRepo.getProblemRecipeDescriptors().stream().filter(d -> d.getProblemType() != null).filter(d -> { +// switch (config.getProblemApplicability(d.getProblemType())) { +// case ON: +// return SpringProjectUtil.isBootProject(project); +// case OFF: +// return false; +// default: // AUTO +// return d.isApplicable(project); +// } +// }).collect(Collectors.toList()); +// } +// +// private void collectProblems(IJavaProject project, List descriptors, IDocument doc, CompilationUnit compilationUnit, Consumer problemHandler) { +// CompilationUnit cu = recipeRepo.mark(project, descriptors, compilationUnit); +// if (compilationUnit != cu) { +// new JavaMarkerVisitor() { +// +// @Override +// public J visit(Tree tree, ExecutionContext context) { +// J t = super.visit(tree, context); +// if (t instanceof J) { +// for (Marker m : t.getMarkers().entries()) { +// if (m instanceof FixAssistMarker) { +// for (ReconcileProblem problem : createProblems(doc, (FixAssistMarker) m, t)) { +// problemHandler.accept(problem); +// } +// } +// } +// } +// return t; +// } +// +// }.visit(cu, new InMemoryExecutionContext(e -> log.error("", e))); +// } +// } +// +// public int getTotalWorkUnits(List docs) { +// return docs.size() * 2; +// } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java index 692fd74bc..82b3b31e9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map.Entry; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.eclipse.lsp4j.CodeAction; @@ -32,7 +33,6 @@ import org.openrewrite.Recipe; import org.openrewrite.RecipeRun; import org.openrewrite.Result; import org.openrewrite.SourceFile; -import org.openrewrite.config.DeclarativeRecipe; import org.openrewrite.internal.InMemoryLargeSourceSet; import org.openrewrite.internal.RecipeIntrospectionUtils; import org.openrewrite.java.JavaParser; @@ -59,6 +59,8 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; +import reactor.core.publisher.Mono; + public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler { public static final String REWRITE_RECIPE_QUICKFIX = "org.openrewrite.rewrite"; @@ -89,29 +91,27 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler } @Override - public QuickfixEdit createEdits(Object p) { + public Mono createEdits(Object p) { if (p instanceof JsonElement) { - return new QuickfixEdit(createEdit((JsonElement) p), null); + return Mono.fromFuture(createEdit((JsonElement) p).thenApply(we -> new QuickfixEdit(we, null))); } return null; } @Override - public void resolve(CodeAction codeAction) { + public CompletableFuture resolve(CodeAction codeAction) { if (codeAction.getData() instanceof JsonElement) { try { - WorkspaceEdit edit = createEdit((JsonElement) codeAction.getData()); - if (edit != null) { - codeAction.setEdit(edit); - } + return createEdit((JsonElement) codeAction.getData()); } catch (Exception e) { log.error("", e); } } + return CompletableFuture.completedFuture(null); } - public WorkspaceEdit createEdit(JsonElement o) { + public CompletableFuture createEdit(JsonElement o) { FixDescriptor data = gson.fromJson(o, FixDescriptor.class); if (data != null && data.getRecipeId() != null) { return perform(data); @@ -139,69 +139,84 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler return workspaceEdit; } - private WorkspaceEdit perform(FixDescriptor data) { + private CompletableFuture perform(FixDescriptor data) { Optional project = projectFinder.find(new TextDocumentIdentifier(data.getDocUris().get(0))); if (project.isPresent()) { boolean projectWide = data.getRecipeScope() == RecipeScope.PROJECT; - Recipe r = createRecipe(data); - if (r == null) { - log.warn("Code Action failed to resolve. Could not create recipe created with id '" + data.getRecipeId() + "'."); - } - List cus = Collections.emptyList(); - if (projectWide) { - JavaParser jp = ORAstUtils.createJavaParserBuilder(project.get()).dependsOn(data.getTypeStubs()).build(); - List inputs = ORAstUtils.getParserInputs(server.getTextDocumentService(), project.get()); - PercentageProgressTask progress = server.getProgressService().createPercentageProgressTask(UUID.randomUUID().toString(), inputs.size() + 1, data.getLabel()); - try { - cus = ORAstUtils.parseInputs(jp, inputs, s -> progress.increment()); - return applyRecipe(r, project.get(), cus); - } finally { - progress.setCurrent(progress.getTotal()); - progress.done(); + return createRecipe(data).thenApply(r -> { + if (r == null) { + log.warn("Code Action failed to resolve. Could not create recipe created with id '" + data.getRecipeId() + "'."); } - } else { - JavaParser jp = ORAstUtils.createJavaParserBuilder(project.get()).dependsOn(data.getTypeStubs()).build(); - List inputs = data.getDocUris().stream().map(URI::create).map(Paths::get).map(p -> ORAstUtils.getParserInput(server.getTextDocumentService(), p)).collect(Collectors.toList()); - cus = ORAstUtils.parseInputs(jp, inputs, null); - return applyRecipe(r, project.get(), cus); - } + List cus = Collections.emptyList(); + if (projectWide) { + JavaParser jp = ORAstUtils.createJavaParserBuilder(project.get()).dependsOn(data.getTypeStubs()).build(); + List inputs = ORAstUtils.getParserInputs(server.getTextDocumentService(), project.get()); + PercentageProgressTask progress = server.getProgressService().createPercentageProgressTask(UUID.randomUUID().toString(), inputs.size() + 1, data.getLabel()); + try { + cus = ORAstUtils.parseInputs(jp, inputs, s -> progress.increment()); + return applyRecipe(r, project.get(), cus); + } finally { + progress.setCurrent(progress.getTotal()); + progress.done(); + } + } else { + JavaParser jp = ORAstUtils.createJavaParserBuilder(project.get()).dependsOn(data.getTypeStubs()).build(); + List inputs = data.getDocUris().stream().map(URI::create).map(Paths::get).map(p -> ORAstUtils.getParserInput(server.getTextDocumentService(), p)).collect(Collectors.toList()); + cus = ORAstUtils.parseInputs(jp, inputs, null); + return applyRecipe(r, project.get(), cus); + } + }); } - return null; + return CompletableFuture.completedFuture(null); } + private CompletableFuture>> findRecipeClass(String className) { + return CompletableFuture.supplyAsync(() -> { + try { + Optional> opt = Optional.of(getClass().getClassLoader().loadClass(className)); + return opt; + } catch (Exception e) { + // ignore + log.info("Didn't find the recipe class '%s' trying recipe repository".formatted(className)); + return Optional.empty(); + } + }); + } - private Recipe createRecipe(FixDescriptor d) { - Recipe r = recipeRepo.getRecipe(d.getRecipeId()).orElse(null); - if (!(r instanceof DeclarativeRecipe)) { - r = RecipeIntrospectionUtils.constructRecipe(r.getClass()); - } - if (d.getParameters() != null) { - for (Entry entry : d.getParameters().entrySet()) { - try { - Field f = r.getClass().getDeclaredField(entry.getKey()); - f.setAccessible(true); - f.set(r, entry.getValue()); - } catch (Exception e) { - log.error("", e);; + private CompletableFuture createRecipe(FixDescriptor d) { + return findRecipeClass(d.getRecipeId()) + .thenCompose(optRecipeClass -> optRecipeClass + .map(recipeClass -> CompletableFuture.completedFuture(RecipeIntrospectionUtils.constructRecipe(recipeClass))) + .orElseGet(() -> recipeRepo.getRecipe(d.getRecipeId()).thenApply(opt -> opt.orElseThrow()))) + .thenApply(r -> { + if (d.getParameters() != null) { + for (Entry entry : d.getParameters().entrySet()) { + try { + Field f = r.getClass().getDeclaredField(entry.getKey()); + f.setAccessible(true); + f.set(r, entry.getValue()); + } catch (Exception e) { + log.error("", e);; + } } } - } - if (d.getRecipeScope() == RecipeScope.NODE) { - if (d.getRangeScope() == null) { - throw new IllegalArgumentException("Missing scope AST node!"); - } else { - r = ORAstUtils.nodeRecipe(r, j -> { - if (j != null) { - Range range = j.getMarkers().findFirst(Range.class).orElse(null); - if (range != null) { - // Rewrite range end offset is up to not including hence -1 - return d.getRangeScope().getStart().getOffset() <= range.getStart().getOffset() && range.getEnd().getOffset() - 1 <= d.getRangeScope().getEnd().getOffset(); - } - } - return false; - }); + if (d.getRecipeScope() == RecipeScope.NODE) { + if (d.getRangeScope() == null) { + throw new IllegalArgumentException("Missing scope AST node!"); + } else { + r = ORAstUtils.nodeRecipe(r, j -> { + if (j != null) { + Range range = j.getMarkers().findFirst(Range.class).orElse(null); + if (range != null) { + // Rewrite range end offset is up to not including hence -1 + return d.getRangeScope().getStart().getOffset() <= range.getStart().getOffset() && range.getEnd().getOffset() - 1 <= d.getRangeScope().getEnd().getOffset(); + } + } + return false; + }); + } } - } - return r; + return r; + }); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java index 47faeaecd..f14600704 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java @@ -49,11 +49,7 @@ public class SpringBootUpgrade { "3.1", "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1" ); - private RewriteRecipeRepository recipeRepo; - public SpringBootUpgrade(SimpleLanguageServer server, RewriteRecipeRepository recipeRepo, JavaProjectFinder projectFinder) { - this.recipeRepo = recipeRepo; - server.onCommand(CMD_UPGRADE_SPRING_BOOT, params -> { String uri = ((JsonElement) params.getArguments().get(0)).getAsString(); Assert.isLegal(uri != null, "Project URI parameter must not be 'null'"); @@ -73,8 +69,8 @@ public class SpringBootUpgrade { + version.toMajorMinorVersionStr() + "' is newer or same as the target version '" + targetVersion.toMajorMinorVersionStr() + "'"); - return recipeRepo.loaded.thenComposeAsync(load -> recipeRepo.apply( - createUpgradeRecipe(version, targetVersion), + return recipeRepo.recipes().thenComposeAsync(recipes -> recipeRepo.apply( + createUpgradeRecipe(recipes, version, targetVersion), uri, UUID.randomUUID().toString() )); @@ -96,7 +92,7 @@ public class SpringBootUpgrade { return ids; } - private Recipe createUpgradeRecipe(Version version, Version targetVersion) { + private Recipe createUpgradeRecipe(Map recipes, Version version, Version targetVersion) { Recipe recipe = new DeclarativeRecipe("upgrade-spring-boot", "Upgrade Spring Boot from " + version + " to " + targetVersion, "", Collections.emptySet(), null, null, false, Collections.emptyList()); @@ -108,7 +104,7 @@ public class SpringBootUpgrade { List recipedIds = createRecipeIdsChain(version.getMajor(), version.getMinor() + 1, targetVersion.getMajor(), targetVersion.getMinor(), versionsToRecipeId); if (!recipedIds.isEmpty()) { String recipeId = recipedIds.get(recipedIds.size() - 1); - getRecipeFromId(recipeId).ifPresent(r -> recipe.getRecipeList().add(r)); + Optional.ofNullable(recipes.get(recipeId)).ifPresent(r -> recipe.getRecipeList().add(r)); } } @@ -121,10 +117,6 @@ public class SpringBootUpgrade { } } - private Optional getRecipeFromId(String recipeId) { - return recipeRepo.getRecipe(recipeId); - } - private static String createVersionString(int major, int minor) { StringBuilder sb = new StringBuilder(); sb.append(major); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java index 905f2fc2f..1fc51d256 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java @@ -254,7 +254,8 @@ public class SpringIndexerJava implements SpringIndexer { } private void scanFile(IJavaProject project, DocumentDescriptor updatedDoc, String content) throws Exception { - ASTParser parser = createParser(project, false); + final boolean ignoreMethodBodies = false; + ASTParser parser = createParser(project, ignoreMethodBodies); String docURI = updatedDoc.getDocURI(); long lastModified = updatedDoc.getLastModified(); @@ -289,7 +290,7 @@ public class SpringIndexerJava implements SpringIndexer { IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator); SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file, - lastModified, docRef, content, generatedSymbols, generatedBeans, problemCollector, SCAN_PASS.ONE, new ArrayList<>()); + lastModified, docRef, content, generatedSymbols, generatedBeans, problemCollector, SCAN_PASS.ONE, new ArrayList<>(), !ignoreMethodBodies); scanAST(context); @@ -316,7 +317,8 @@ public class SpringIndexerJava implements SpringIndexer { } public List computeSymbols(IJavaProject project, String docURI, String content) throws Exception { - ASTParser parser = createParser(project, false); + final boolean ignoreMethodBodies = false; + ASTParser parser = createParser(project, ignoreMethodBodies); if (content != null) { String unitName = docURI.substring(docURI.lastIndexOf("/")); @@ -347,7 +349,7 @@ public class SpringIndexerJava implements SpringIndexer { AtomicReference docRef = new AtomicReference<>(); String file = UriUtil.toFileString(docURI); SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file, - 0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, SCAN_PASS.ONE, new ArrayList<>()); + 0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, SCAN_PASS.ONE, new ArrayList<>(), !ignoreMethodBodies); scanAST(context); @@ -359,7 +361,8 @@ public class SpringIndexerJava implements SpringIndexer { } private Set scanFilesInternally(IJavaProject project, DocumentDescriptor[] docs) throws Exception { - ASTParser parser = createParser(project, false); + final boolean ignoreMethodBodies = false; + ASTParser parser = createParser(project, ignoreMethodBodies); // this is to keep track of already scanned files to avoid endless loops due to circular dependencies Set scannedTypes = new HashSet<>(); @@ -404,7 +407,7 @@ public class SpringIndexerJava implements SpringIndexer { IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator); SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath, - lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, SCAN_PASS.ONE, new ArrayList<>()); + lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, SCAN_PASS.ONE, new ArrayList<>(), !ignoreMethodBodies); scanAST(context); @@ -537,7 +540,8 @@ public class SpringIndexerJava implements SpringIndexer { javaFiles.length, "Spring Tools: Indexing Java Sources for '" + project.getElementName() + "'"); try { - ASTParser parser = createParser(project, SCAN_PASS.ONE.equals(pass)); + final boolean ignoreMethodBodies = SCAN_PASS.ONE.equals(pass); + ASTParser parser = createParser(project, ignoreMethodBodies); List nextPassFiles = new ArrayList<>(); FileASTRequestor requestor = new FileASTRequestor() { @@ -552,7 +556,7 @@ public class SpringIndexerJava implements SpringIndexer { IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator); SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath, - lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, pass, nextPassFiles); + lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, pass, nextPassFiles, !ignoreMethodBodies); scanAST(context); progressTask.increment(); @@ -674,7 +678,7 @@ public class SpringIndexerJava implements SpringIndexer { try { problemCollector.beginCollecting(); - reconciler.reconcile(context.getProject(), URI.create(context.getDocURI()), context.getCu(), problemCollector, context.getPass() == SCAN_PASS.TWO); + reconciler.reconcile(context.getProject(), URI.create(context.getDocURI()), context.getCu(), problemCollector, context.isFullAst()); problemCollector.endCollecting(); } catch (RequiredCompleteAstException e) { if (context.getPass() == SCAN_PASS.TWO) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java index 10ef6daf5..99d426bbb 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java @@ -40,6 +40,7 @@ public class SpringIndexerJavaContext { private final IProblemCollector getProblemCollector; private final SCAN_PASS pass; private final List nextPassFiles; + private final boolean fullAst; private final Set dependencies = new HashSet<>(); private final Set scannedTypes = new HashSet<>(); @@ -57,7 +58,8 @@ public class SpringIndexerJavaContext { List beans, IProblemCollector problemCollector, SCAN_PASS pass, - List nextPassFiles + List nextPassFiles, + boolean fullAst ) { super(); this.project = project; @@ -72,6 +74,7 @@ public class SpringIndexerJavaContext { this.beans = beans; this.pass = pass; this.nextPassFiles = nextPassFiles; + this.fullAst = fullAst; } public IJavaProject getProject() { @@ -148,4 +151,7 @@ public class SpringIndexerJavaContext { return this.getProblemCollector; } + public boolean isFullAst() { + return fullAst; + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/AppPropertiesQuickFixes.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/AppPropertiesQuickFixes.java index 3c6631917..7cf8f9f77 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/AppPropertiesQuickFixes.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/AppPropertiesQuickFixes.java @@ -25,6 +25,8 @@ import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.JsonElement; +import reactor.core.publisher.Mono; + /** * Boot app Properties file quick fix handlers * @@ -47,7 +49,7 @@ public class AppPropertiesQuickFixes { public AppPropertiesQuickFixes(QuickfixRegistry r, CommonQuickfixes commonFixes) { MISSING_PROPERTY = commonFixes.MISSING_PROPERTY; - DEPRECATED_PROPERTY = r.register("DEPRECATED_PROPERTY", (Object _params) -> { + DEPRECATED_PROPERTY = r.register("DEPRECATED_PROPERTY", (Object _params) -> Mono.fromSupplier(() -> { DeprecatedPropertyData params = gson.fromJson((JsonElement)_params, DeprecatedPropertyData.class); try { if (params.getRange() != null && params.getReplacement() != null) { @@ -64,7 +66,7 @@ public class AppPropertiesQuickFixes { log.error("", e); } return NULL_FIX; - }); + })); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/CommonQuickfixes.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/CommonQuickfixes.java index 053da7a38..1cb500973 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/CommonQuickfixes.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/CommonQuickfixes.java @@ -47,6 +47,8 @@ import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.JsonElement; +import reactor.core.publisher.Mono; + /** * Common quick fixes for YAML and properties * @@ -96,7 +98,7 @@ public class CommonQuickfixes { } if (clientCapabilities.getWorkspace().getWorkspaceEdit() != null && clientCapabilities.getWorkspace().getWorkspaceEdit().getResourceOperations() != null && clientCapabilities.getWorkspace().getWorkspaceEdit().getResourceOperations().contains(ResourceOperationKind.Create) && Boolean.TRUE.equals(clientCapabilities.getWorkspace().getWorkspaceEdit().getDocumentChanges())) { - MISSING_PROPERTY = r.register(MISSING_PROPERTY_APP_QF_ID, (Object _params) -> { + MISSING_PROPERTY = r.register(MISSING_PROPERTY_APP_QF_ID, (Object _params) -> Mono.fromSupplier(() -> { MissingPropertyData params = gson.fromJson((JsonElement)_params, MissingPropertyData.class); try { Optional p = projectFinder.find(params.getDoc()); @@ -138,7 +140,7 @@ public class CommonQuickfixes { log.error("", e); } return NULL_FIX; - }); + })); } else { MISSING_PROPERTY = null; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/AppYamlQuickfixes.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/AppYamlQuickfixes.java index 5acec42c0..731c9d17a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/AppYamlQuickfixes.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/AppYamlQuickfixes.java @@ -38,6 +38,8 @@ import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.JsonElement; +import reactor.core.publisher.Mono; + /** * Boot YAML file properties quick fix code action handlers * @@ -60,7 +62,7 @@ public class AppYamlQuickfixes { public AppYamlQuickfixes(QuickfixRegistry r, SimpleTextDocumentService textDocumentService, YamlStructureProvider structureProvider, CommonQuickfixes commonQuickfixes) { MISSING_PROPERTY = commonQuickfixes.MISSING_PROPERTY; - DEPRECATED_PROPERTY = r.register("DEPRECATED_YAML_PROPERTY", (Object _params) -> { + DEPRECATED_PROPERTY = r.register("DEPRECATED_YAML_PROPERTY", (Object _params) -> Mono.fromSupplier(() -> { DeprecatedPropertyData params = gson.fromJson((JsonElement)_params, DeprecatedPropertyData.class); try { TextDocument _doc = textDocumentService.getLatestSnapshot(params.getUri()); @@ -112,7 +114,7 @@ public class AppYamlQuickfixes { log.error("", e); } return NULL_FIX; - }); + })); } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java index ea9424b57..470a9e7f4 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java @@ -161,7 +161,7 @@ public class ValueSpelExpressionValidationTest { new JdtReconciler(compilationUnitCache, config, new JdtAstReconciler[] { new AnnotationNodeReconciler(config) }) - }, server); + }); } @AfterEach