Lazy load recipes. Bug fixes for reconciling
This commit is contained in:
@@ -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<QuickfixEdit> createEdits(Object params);
|
||||
}
|
||||
|
||||
@@ -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<QuickfixEdit> 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<QuickfixEdit> createEdits(Object params) {
|
||||
return handler.createEdits(params);
|
||||
}
|
||||
|
||||
@@ -68,9 +68,7 @@ public class QuickfixRegistry {
|
||||
|
||||
public Mono<QuickfixEdit> 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() {
|
||||
|
||||
@@ -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<WorkspaceEdit> resolve(CodeAction codeAction);
|
||||
|
||||
}
|
||||
|
||||
@@ -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<CodeAction> resolveCodeAction(CodeAction ca) {
|
||||
return CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> {
|
||||
if (appContext!=null) {
|
||||
Map<String, CodeActionResolver> 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.<String, CodeActionResolver>emptyMap() : appContext.getBeansOfType(CodeActionResolver.class))
|
||||
.thenCompose(resolvers -> {
|
||||
List<CompletableFuture<Void>> 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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<RewriteRecipeRepository> 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()));
|
||||
|
||||
@@ -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<ICompletionEngine> 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));
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Path> files = IClasspathUtil.getProjectJavaSourceFolders(project.getClasspath()).flatMap(folder -> {
|
||||
// try {
|
||||
// return Files.walk(folder.toPath()).filter(Files::isRegularFile);
|
||||
// } catch (IOException e) {
|
||||
// return Stream.empty();
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// Stream<TextDocumentIdentifier> docIds = files
|
||||
// .filter(f -> f.getFileName().toString().endsWith(".java"))
|
||||
// .map(f -> new TextDocumentIdentifier(f.toUri().toASCIIString()));
|
||||
//
|
||||
// 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(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<IDocument, Collection<ReconcileProblem>> 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()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, Recipe> recipes;
|
||||
|
||||
final private JavaProjectFinder projectFinder;
|
||||
|
||||
final private List<RecipeCodeActionDescriptor> codeActionDescriptors;
|
||||
|
||||
final private ListenerList<Void> loadListeners;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
CompletableFuture<Void> loaded;
|
||||
private CompletableFuture<Map<String, Recipe>> recipesFuture = null;
|
||||
|
||||
private Set<String> scanFiles;
|
||||
private Set<String> 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<Void> firstConfigLoaded = new CompletableFuture<>();
|
||||
|
||||
config.addListener(l -> {
|
||||
Set<String> 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<Void> 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<String, Recipe> loadRecipes() {
|
||||
IndefiniteProgressTask progressTask = server.getProgressService().createIndefiniteProgressTask(UUID.randomUUID().toString(), "Loading Rewrite Recipes", null);
|
||||
Map<String, Recipe> 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<Recipe> getRecipe(String name) {
|
||||
return Optional.ofNullable(recipes.get(name));
|
||||
public CompletableFuture<Map<String, Recipe>> 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<RecipeCodeActionDescriptor> getProblemRecipeDescriptors() {
|
||||
List<RecipeCodeActionDescriptor> 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<RecipeCodeActionDescriptor> getCodeActionRecipeDescriptors() {
|
||||
List<RecipeCodeActionDescriptor> 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<? extends RecipeCodeActionDescriptor> descriptors, CompilationUnit compilationUnit) {
|
||||
CompilationUnit cu = compilationUnit;
|
||||
for (RecipeCodeActionDescriptor d : descriptors) {
|
||||
TreeVisitor<?, ExecutionContext> 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<Optional<Recipe>> 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<Recipe> listProjectRefactoringRecipes(String uri) {
|
||||
private CompletableFuture<List<Recipe>> 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<IJavaProject> 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<Path, Parser.Input> 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) {
|
||||
|
||||
@@ -105,75 +105,77 @@ public class RewriteReconciler implements JavaReconciler {
|
||||
// }
|
||||
}
|
||||
|
||||
private List<ReconcileProblem> 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<ReconcileProblem> 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<IDocument, Collection<ReconcileProblem>> reconcile(IJavaProject project, List<TextDocument> docs, Runnable incrementProgress) {
|
||||
|
||||
if (!config.isJavaSourceReconcileEnabled()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
// if (!config.isJavaSourceReconcileEnabled()) {
|
||||
// return Collections.emptyMap();
|
||||
// }
|
||||
//
|
||||
// long start = System.currentTimeMillis();
|
||||
//
|
||||
// Map<IDocument, Collection<ReconcileProblem>> allProblems = new HashMap<>();
|
||||
// List<Path> testSourceFolders = IClasspathUtil.getProjectTestJavaSources(project.getClasspath()).map(f -> f.toPath()).collect(Collectors.toList());
|
||||
// List<TextDocument> testSources = new ArrayList<>(docs.size());
|
||||
// List<TextDocument> 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<Path> 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<IDocument, Collection<ReconcileProblem>> allProblems = new HashMap<>();
|
||||
List<Path> testSourceFolders = IClasspathUtil.getProjectTestJavaSources(project.getClasspath()).map(f -> f.toPath()).collect(Collectors.toList());
|
||||
List<TextDocument> testSources = new ArrayList<>(docs.size());
|
||||
List<TextDocument> 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<Path> 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<IDocument, Collection<ReconcileProblem>> doReconcile(IJavaProject project, List<TextDocument> docs, JavaParser javaParser, JavaSourceSet javaSourceSet, Runnable incrementProgress) {
|
||||
Map<IDocument, Collection<ReconcileProblem>> allProblems = new HashMap<>();
|
||||
if (javaParser != null && config.isJavaSourceReconcileEnabled()) {
|
||||
try {
|
||||
List<RecipeCodeActionDescriptor> descriptors = getProblemRecipeDescriptors(project);
|
||||
|
||||
|
||||
if (!descriptors.isEmpty()) {
|
||||
|
||||
for (int i = 0; i < docs.size(); i += BATCH) {
|
||||
List<TextDocument> batchList = docs.subList(i, Math.min(i + BATCH, docs.size()));
|
||||
|
||||
List<CompilationUnit> 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<ReconcileProblem> 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<RecipeCodeActionDescriptor> 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<RecipeCodeActionDescriptor> descriptors, IDocument doc, CompilationUnit compilationUnit, Consumer<ReconcileProblem> problemHandler) {
|
||||
CompilationUnit cu = recipeRepo.mark(project, descriptors, compilationUnit);
|
||||
if (compilationUnit != cu) {
|
||||
new JavaMarkerVisitor<ExecutionContext>() {
|
||||
|
||||
@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<TextDocument> docs) {
|
||||
return docs.size() * 2;
|
||||
}
|
||||
// private Map<IDocument, Collection<ReconcileProblem>> doReconcile(IJavaProject project, List<TextDocument> docs, JavaParser javaParser, JavaSourceSet javaSourceSet, Runnable incrementProgress) {
|
||||
// Map<IDocument, Collection<ReconcileProblem>> allProblems = new HashMap<>();
|
||||
// if (javaParser != null && config.isJavaSourceReconcileEnabled()) {
|
||||
// try {
|
||||
// List<RecipeCodeActionDescriptor> descriptors = getProblemRecipeDescriptors(project);
|
||||
//
|
||||
//
|
||||
// if (!descriptors.isEmpty()) {
|
||||
//
|
||||
// for (int i = 0; i < docs.size(); i += BATCH) {
|
||||
// List<TextDocument> batchList = docs.subList(i, Math.min(i + BATCH, docs.size()));
|
||||
//
|
||||
// List<CompilationUnit> 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<ReconcileProblem> 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<RecipeCodeActionDescriptor> 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<RecipeCodeActionDescriptor> descriptors, IDocument doc, CompilationUnit compilationUnit, Consumer<ReconcileProblem> problemHandler) {
|
||||
// CompilationUnit cu = recipeRepo.mark(project, descriptors, compilationUnit);
|
||||
// if (compilationUnit != cu) {
|
||||
// new JavaMarkerVisitor<ExecutionContext>() {
|
||||
//
|
||||
// @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<TextDocument> docs) {
|
||||
// return docs.size() * 2;
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -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<QuickfixEdit> 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<WorkspaceEdit> 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<WorkspaceEdit> 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<WorkspaceEdit> perform(FixDescriptor data) {
|
||||
Optional<IJavaProject> 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<CompilationUnit> cus = Collections.emptyList();
|
||||
if (projectWide) {
|
||||
JavaParser jp = ORAstUtils.createJavaParserBuilder(project.get()).dependsOn(data.getTypeStubs()).build();
|
||||
List<Input> 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<Input> 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<CompilationUnit> cus = Collections.emptyList();
|
||||
if (projectWide) {
|
||||
JavaParser jp = ORAstUtils.createJavaParserBuilder(project.get()).dependsOn(data.getTypeStubs()).build();
|
||||
List<Input> 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<Input> 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<Optional<Class<?>>> findRecipeClass(String className) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
Optional<Class<?>> 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<String, Object> 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<Recipe> 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<String, Object> 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Recipe> 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<String> 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<Recipe> getRecipeFromId(String recipeId) {
|
||||
return recipeRepo.getRecipe(recipeId);
|
||||
}
|
||||
|
||||
private static String createVersionString(int major, int minor) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(major);
|
||||
|
||||
@@ -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<EnhancedSymbolInformation> 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<TextDocument> 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<String> 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<String> 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<String> 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) {
|
||||
|
||||
@@ -40,6 +40,7 @@ public class SpringIndexerJavaContext {
|
||||
private final IProblemCollector getProblemCollector;
|
||||
private final SCAN_PASS pass;
|
||||
private final List<String> nextPassFiles;
|
||||
private final boolean fullAst;
|
||||
|
||||
private final Set<String> dependencies = new HashSet<>();
|
||||
private final Set<String> scannedTypes = new HashSet<>();
|
||||
@@ -57,7 +58,8 @@ public class SpringIndexerJavaContext {
|
||||
List<CachedBean> beans,
|
||||
IProblemCollector problemCollector,
|
||||
SCAN_PASS pass,
|
||||
List<String> nextPassFiles
|
||||
List<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<IJavaProject> p = projectFinder.find(params.getDoc());
|
||||
@@ -138,7 +140,7 @@ public class CommonQuickfixes {
|
||||
log.error("", e);
|
||||
}
|
||||
return NULL_FIX;
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
MISSING_PROPERTY = null;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public class ValueSpelExpressionValidationTest {
|
||||
new JdtReconciler(compilationUnitCache, config, new JdtAstReconciler[] {
|
||||
new AnnotationNodeReconciler(config)
|
||||
})
|
||||
}, server);
|
||||
});
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
||||
Reference in New Issue
Block a user