diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml
index 7f174019f..cba608063 100644
--- a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml
+++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml
@@ -131,6 +131,12 @@
id="org.springframework.tooling.boot.ls.preferences"
name="Spring Boot Language Server">
+
+
@@ -338,9 +344,13 @@
+
+
diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/BootJavaPreferencesPage.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/BootJavaPreferencesPage.java
index 32584f1d6..bd30ff71f 100644
--- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/BootJavaPreferencesPage.java
+++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/BootJavaPreferencesPage.java
@@ -96,7 +96,6 @@ public class BootJavaPreferencesPage extends FieldEditorPreferencePage implement
addField(new BooleanFieldEditor(Constants.PREF_VALIDATION_SPEL_EXPRESSIONS, "SpEL Expression Syntax Validation", fieldEditorParent));
- addField(new BooleanFieldEditor(Constants.PREF_REWRITE_RECONCILE, "Experimental reconciling for Java source based on Rewrite project", fieldEditorParent));
}
}
diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java
index 5019d61b4..4174ddb3f 100644
--- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java
+++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java
@@ -33,4 +33,7 @@ public class Constants {
public static final String PREF_REWRITE_RECONCILE = "boot-java.rewrite.reconcile";
+ public static final String PREF_REWRITE_RECIPES_SCAN_FILES = "boot-java.rewrite.scan-files";
+
+ public static final String PREF_REWRITE_RECIPES_SCAN_DIRS = "boot-java.rewrite.scan-directories";
}
diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java
index 242911bc2..2564aa151 100644
--- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java
+++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java
@@ -32,9 +32,9 @@ import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage;
import org.eclipse.lsp4j.services.LanguageServer;
import org.springframework.tooling.boot.ls.prefs.CategoryProblemsSeverityPrefsPage;
+import org.springframework.tooling.boot.ls.prefs.FileListEditor;
import org.springframework.tooling.boot.ls.prefs.ProblemCategoryData;
import org.springframework.tooling.boot.ls.prefs.ProblemCategoryData.CategoryToggleData;
-import org.springframework.tooling.ls.eclipse.commons.LanguageServerCommonsActivator;
import org.springsource.ide.eclipse.commons.boot.ls.remoteapps.RemoteBootAppsDataHolder;
import org.springsource.ide.eclipse.commons.boot.ls.remoteapps.RemoteBootAppsDataHolder.RemoteAppData;
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
@@ -187,7 +187,12 @@ public class DelegatingStreamConnectionProvider implements StreamConnectionProvi
bootJavaObj.put("change-detection", bootChangeDetection);
bootJavaObj.put("validation", validation);
bootJavaObj.put("remote-apps", getAllRemoteApps());
- bootJavaObj.put("rewrite", Map.of("reconcile", preferenceStore.getBoolean(Constants.PREF_REWRITE_RECONCILE)));
+
+ bootJavaObj.put("rewrite", Map.of(
+ "reconcile", preferenceStore.getBoolean(Constants.PREF_REWRITE_RECONCILE),
+ "scan-directories", FileListEditor.getValuesFromPreference(preferenceStore.getString(Constants.PREF_REWRITE_RECIPES_SCAN_DIRS)),
+ "scan-files", FileListEditor.getValuesFromPreference(preferenceStore.getString(Constants.PREF_REWRITE_RECIPES_SCAN_FILES))
+ ));
settings.put("boot-java", bootJavaObj);
putValidationPreferences(settings);
diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java
new file mode 100644
index 000000000..06708f8cc
--- /dev/null
+++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java
@@ -0,0 +1,33 @@
+package org.springframework.tooling.boot.ls;
+
+import java.util.List;
+
+import org.eclipse.jface.preference.BooleanFieldEditor;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.jface.preference.PathEditor;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.springframework.tooling.boot.ls.prefs.FileListEditor;
+
+public class RewritePreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
+
+ @Override
+ public void init(IWorkbench workbench) {
+ setPreferenceStore(BootLanguageServerPlugin.getDefault().getPreferenceStore());
+ }
+
+ @Override
+ protected void createFieldEditors() {
+ Composite fieldEditorParent = getFieldEditorParent();
+
+ addField(new BooleanFieldEditor(Constants.PREF_REWRITE_RECONCILE, "Experimental reconciling for Java source based on Rewrite project", fieldEditorParent));
+
+ addField(new FileListEditor(Constants.PREF_REWRITE_RECIPES_SCAN_FILES, "JAR and YAML files to scan for Recipes", "Select JARs and YAML files:", fieldEditorParent, List.of("jar", "yml", "yaml")));
+
+ addField(new PathEditor(Constants.PREF_REWRITE_RECIPES_SCAN_DIRS, "Directories to scan for Recipes", "Select directory to scan for Recipes", fieldEditorParent));
+
+ }
+
+
+}
diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteCommandsQuickAccessProvider.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteCommandsQuickAccessProvider.java
new file mode 100644
index 000000000..24c4429fb
--- /dev/null
+++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteCommandsQuickAccessProvider.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.tooling.boot.ls.commands;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.lsp4e.LanguageServiceAccessor;
+import org.eclipse.lsp4j.ExecuteCommandParams;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.eclipse.ui.quickaccess.IQuickAccessComputer;
+import org.eclipse.ui.quickaccess.IQuickAccessComputerExtension;
+import org.eclipse.ui.quickaccess.QuickAccessElement;
+import org.springframework.tooling.boot.ls.BootLanguageServerPlugin;
+
+@SuppressWarnings("restriction")
+public class RewriteCommandsQuickAccessProvider implements IQuickAccessComputer, IQuickAccessComputerExtension {
+
+ private static final String CMD_REWRITE_RELOAD = "sts/rewrite/reload";
+
+ @Override
+ public QuickAccessElement[] computeElements(String query, IProgressMonitor monitor) {
+ return new QuickAccessElement[] {
+ new QuickAccessElement() {
+
+ @Override
+ public String getLabel() {
+ return "Reload Rewrite Recipes";
+ }
+
+ @Override
+ public ImageDescriptor getImageDescriptor() {
+ return null;
+ }
+
+ @Override
+ public String getId() {
+ return CMD_REWRITE_RELOAD;
+ }
+
+ @Override
+ public void execute() {
+ List usedLanguageServers = LanguageServiceAccessor.getActiveLanguageServers(serverCapabilities -> true);
+
+ if (usedLanguageServers.isEmpty()) {
+ return;
+ }
+
+ ExecuteCommandParams commandParams = new ExecuteCommandParams();
+ commandParams.setCommand(CMD_REWRITE_RELOAD);
+
+ commandParams.setArguments(Collections.emptyList());
+
+ try {
+ CompletableFuture.allOf(usedLanguageServers.stream().map(ls ->
+ ls.getWorkspaceService().executeCommand(commandParams)).toArray(CompletableFuture[]::new)).get(2, TimeUnit.SECONDS);
+ }
+ catch (Exception e) {
+ BootLanguageServerPlugin.getDefault().getLog().error("Failed to reload Rewrite Recipes!", e);
+ }
+ }
+ }
+ };
+ }
+
+ @Override
+ public QuickAccessElement[] computeElements() {
+ return new QuickAccessElement[0];
+ }
+
+ @Override
+ public void resetState() {
+ }
+
+ @Override
+ public boolean needsRefresh() {
+ return false;
+ }
+
+}
diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java
index 6e999f3d4..21d509b17 100644
--- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java
+++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java
@@ -48,6 +48,7 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
+@SuppressWarnings("restriction")
public class RewriteRefactoringsHandler extends AbstractHandler {
private static class DurationTypeConverter implements JsonSerializer, JsonDeserializer {
diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java
index 9a7a25e2b..8719e76ec 100644
--- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java
+++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java
@@ -45,6 +45,7 @@ import org.osgi.framework.FrameworkUtil;
import org.springframework.tooling.boot.ls.commands.RecipeDescriptor.OptionDescriptor;
import org.springframework.tooling.boot.ls.commands.RecipeTreeModel.CheckedState;
+@SuppressWarnings("restriction")
public class SelectRecipesDialog extends StatusDialog {
private static final int MARGIN = 5;
diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/FileListEditor.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/FileListEditor.java
new file mode 100644
index 000000000..429473f76
--- /dev/null
+++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/FileListEditor.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.tooling.boot.ls.prefs;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jface.preference.PathEditor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.FileDialog;
+
+public class FileListEditor extends PathEditor {
+
+ private String lastPath;
+
+ private String dirChooserLabelText;
+
+ private List fileFilters;
+
+ public FileListEditor(String name, String labelText,
+ String dirChooserLabelText, Composite parent, List fileFilters) {
+ super(name, labelText, dirChooserLabelText, parent);
+ this.dirChooserLabelText = dirChooserLabelText;
+ this.fileFilters = fileFilters;
+ }
+
+ @Override
+ protected String getNewInputObject() {
+
+ FileDialog dialog = new FileDialog(getShell(), SWT.SHEET);
+ dialog.setFilterExtensions(fileFilters.toArray(String[]::new));
+ if (dirChooserLabelText != null) {
+ dialog.setText(dirChooserLabelText);
+ }
+ if (lastPath != null) {
+ if (new File(lastPath).exists()) {
+ dialog.setFilterPath(lastPath);
+ }
+ }
+ String file = dialog.open();
+ if (file != null) {
+ String parentFolder = new File(file).getParent();
+ if (parentFolder == null) {
+ return null;
+ }
+ lastPath = parentFolder;
+ }
+ return file;
+ }
+
+ public static List getValuesFromPreference(String rawValue) {
+ StringTokenizer st = new StringTokenizer(rawValue, File.pathSeparator
+ + "\n\r");//$NON-NLS-1$
+ ArrayList l = new ArrayList<>();
+ while (st.hasMoreElements()) {
+ l.add((String)st.nextElement());
+ }
+ return l;
+ }
+
+}
diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java
index cca405180..fb20bc573 100644
--- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java
+++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java
@@ -31,13 +31,16 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
import org.eclipse.lsp4j.ClientCapabilities;
+import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionOptions;
import org.eclipse.lsp4j.CodeLensOptions;
+import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.ExecuteCommandOptions;
@@ -94,6 +97,7 @@ import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
+import com.google.common.collect.ImmutableList;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -720,10 +724,21 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
d.setSeverity(severity);
d.setSource(getServer().EXTENSION_ID);
List> fixes = problem.getQuickfixes();
+ // Copy original diagnsotic without the data field to avoid stackoverflow is hashCode() method call
+ Diagnostic refDiagnostic = new Diagnostic(d.getRange(), d.getMessage(), d.getSeverity(), d.getSource());
if (CollectionUtil.hasElements(fixes)) {
- for (QuickfixData> fix : fixes) {
- quickfixes.add(new Quickfix<>(CODE_ACTION_COMMAND_ID, d, fix));
- }
+ d.setData(fixes.stream().map(fix -> {
+ CodeAction ca = new CodeAction();
+ ca.setKind(CodeActionKind.QuickFix);
+ ca.setTitle(fix.title);
+ ca.setDiagnostics(List.of(refDiagnostic));
+ ca.setCommand(new Command(
+ fix.title,
+ CODE_ACTION_COMMAND_ID,
+ ImmutableList.of(fix.type.getId(), fix.params)
+ ));
+ return ca;
+ }).collect(Collectors.toList()));
}
diagnostics.add(d);
}
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 6b06023de..4e639ecb9 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
@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.util;
+import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -81,11 +82,15 @@ import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.BadLocationException;
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.Region;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
public class SimpleTextDocumentService implements TextDocumentService, DocumentEventListenerManager {
@@ -430,22 +435,26 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE
}
}
- private List> computeCodeActions(CancelChecker cancelToken, CodeActionCapabilities capabilities, TrackedDocument doc, CodeActionParams params) {
+ private List> computeCodeActions(CancelChecker cancelToken, CodeActionCapabilities capabilities, TextDocument doc, CodeActionParams params) {
Builder> listBuilder = ImmutableList.builder();
CodeActionContext context = params.getContext();
if (!context.getDiagnostics().isEmpty() || (context.getOnly() != null && context.getOnly().contains(CodeActionKind.QuickFix))) {
- doc.getQuickfixes().stream()
- .filter((fix) -> fix.appliesTo(params.getRange(), context))
- .map(f -> f.getCodeAction(params.getContext()))
- .map(command -> Either.forRight(command))
- .forEach(listBuilder::add);
+ params.getContext().getDiagnostics().forEach(d -> {
+ if (d.getData() != null) {
+ Type type = new TypeToken>(){}.getType();
+ List codeActions = new GsonBuilder().create().fromJson((JsonElement)d.getData(), type);
+ for (CodeAction ca : codeActions) {
+ listBuilder.add(Either.forRight(ca));
+ }
+ }
+ });
}
if (codeActionHandler != null) {
try {
- int start = doc.getDocument().toOffset(params.getRange().getStart());
- int end = doc.getDocument().toOffset(params.getRange().getEnd());
- listBuilder.addAll(codeActionHandler.handle(cancelToken, capabilities, context, doc.getDocument(), new Region(start, end - start)));
+ int start = doc.toOffset(params.getRange().getStart());
+ int end = doc.toOffset(params.getRange().getEnd());
+ listBuilder.addAll(codeActionHandler.handle(cancelToken, capabilities, context, doc, new Region(start, end - start)));
} catch (Exception e) {
log.error("Failed to compute quick refactorings", e);
}
@@ -469,14 +478,22 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE
// this doesn't happen async, because it accesses the internal documents structure
// and therefore needs to be executed as part of the main LSP message queue
- TrackedDocument doc = documents.get(params.getTextDocument().getUri());
+ String uri = params.getTextDocument().getUri();
+ TrackedDocument trackedDoc = documents.get(uri);
+ TextDocument doc = trackedDoc == null ? null : trackedDoc.getDocument();
+
+ if (doc == null) {
+ if (uri.endsWith(".java")) {
+ doc = new LazyTextDocument(uri, LanguageId.JAVA);
+ }
+ }
if (doc != null) {
-
+ final TextDocument d = doc;
return server.getClientCapabilities()
.thenApply(SimpleTextDocumentService::getCodeActionCapabilities)
.thenComposeAsync(capabilities ->
- CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> computeCodeActions(cancelToken, capabilities, doc, params)));
+ CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> computeCodeActions(cancelToken, capabilities, d, params)));
} else {
return CompletableFuture.completedFuture(ImmutableList.of());
}
diff --git a/headless-services/commons/commons-rewrite-test/pom.xml b/headless-services/commons/commons-rewrite-test/pom.xml
new file mode 100644
index 000000000..8ffb8483e
--- /dev/null
+++ b/headless-services/commons/commons-rewrite-test/pom.xml
@@ -0,0 +1,22 @@
+
+ 4.0.0
+ qq
+ commons-rewrite-test
+
+
+ org.springframework.ide.vscode
+ commons-parent
+ 1.39.0-SNAPSHOT
+ ../pom.xml
+
+
+
+
+ org.springframework.ide.vscode
+ commons-rewrite
+ ${project.version}
+
+
+
+
+
\ No newline at end of file
diff --git a/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameProblemDescriptor.java b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameProblemDescriptor.java
new file mode 100644
index 000000000..4df00128c
--- /dev/null
+++ b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameProblemDescriptor.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.rewrite.test;
+
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Tree;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.JavaVisitor;
+import org.openrewrite.java.tree.J.MethodDeclaration;
+import org.openrewrite.marker.Range;
+import org.springframework.ide.vscode.commons.java.IJavaProject;
+import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemCategory;
+import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity;
+import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
+import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTypes;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor;
+import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
+
+public class HelloMethodRenameProblemDescriptor implements RecipeSpringJavaProblemDescriptor {
+
+ @Override
+ public String getRecipeId() {
+ return "org.springframework.rewrite.test.HelloMethodRenameRecipe";
+ }
+
+ @Override
+ public String getLabel(RecipeScope s) {
+ return RecipeCodeActionDescriptor.buildLabel("Switch hello method into bye", s);
+ }
+
+ @Override
+ public RecipeScope[] getScopes() {
+ return RecipeScope.values();
+ }
+
+ @Override
+ public JavaVisitor getMarkerVisitor() {
+ return new JavaIsoVisitor<>() {
+
+ @Override
+ public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, ExecutionContext p) {
+ MethodDeclaration m = super.visitMethodDeclaration(method, p);
+ if ("hello".equals(method.getSimpleName())) {
+ FixAssistMarker marker = new FixAssistMarker(Tree.randomId()).withRecipeId(getRecipeId()).withScope(m.getMarkers().findFirst(Range.class).get());
+ m = m.withName(m.getName().withMarkers(m.getName().getMarkers().add(marker)));
+ }
+ return m;
+ }
+
+ };
+ }
+
+ @Override
+ public boolean isApplicable(IJavaProject project) {
+ return true;
+ }
+
+ @Override
+ public ProblemType getProblemType() {
+ return ProblemTypes.create("Hello Method!", ProblemSeverity.ERROR, ProblemCategory.NO_CATEGORY);
+ }
+
+}
diff --git a/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameRecipe.java b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameRecipe.java
new file mode 100644
index 000000000..e7f5d9822
--- /dev/null
+++ b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameRecipe.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.rewrite.test;
+
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.tree.J.MethodDeclaration;
+import org.openrewrite.java.tree.J.MethodInvocation;
+
+public class HelloMethodRenameRecipe extends Recipe {
+
+ @Override
+ public String getDisplayName() {
+ return "Rename hello method into bye";
+ }
+
+ @Override
+ protected TreeVisitor, ExecutionContext> getVisitor() {
+ return new JavaIsoVisitor<>() {
+
+ @Override
+ public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, ExecutionContext p) {
+ MethodDeclaration m = super.visitMethodDeclaration(method, p);
+ if ("hello".equals(m.getSimpleName())) {
+ m = m.withName(m.getName().withSimpleName("bye"));
+ }
+ return m;
+ }
+
+ @Override
+ public MethodInvocation visitMethodInvocation(MethodInvocation method, ExecutionContext p) {
+ MethodInvocation m = super.visitMethodInvocation(method, p);
+ if ("hello".equals(m.getSimpleName())) {
+ m = m.withName(m.getName().withSimpleName("bye"));
+ }
+ return m;
+ }
+
+ };
+ }
+
+
+
+}
diff --git a/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/OssPackageRecipe.java b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/OssPackageRecipe.java
new file mode 100644
index 000000000..e8cef39a6
--- /dev/null
+++ b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/OssPackageRecipe.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.rewrite.test;
+
+import org.openrewrite.Recipe;
+import org.openrewrite.java.ChangePackage;
+
+public class OssPackageRecipe extends Recipe {
+
+ public OssPackageRecipe() {
+ doNext(new ChangePackage("com.example", "org.example", true));
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Test recipe class coming from the Jar";
+ }
+
+}
diff --git a/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/StsTestCodeActionRepo.java b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/StsTestCodeActionRepo.java
new file mode 100644
index 000000000..b2779e573
--- /dev/null
+++ b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/StsTestCodeActionRepo.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.rewrite.test;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.ide.vscode.commons.rewrite.config.CodeActionRepository;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor;
+
+public class StsTestCodeActionRepo extends CodeActionRepository {
+
+ @Override
+ public List getCodeActionDescriptors() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getProblemDescriptors() {
+ return List.of(new HelloMethodRenameProblemDescriptor());
+ }
+
+}
diff --git a/headless-services/commons/commons-rewrite-test/src/main/resources/META-INF/rewrite/test-recipes.yml b/headless-services/commons/commons-rewrite-test/src/main/resources/META-INF/rewrite/test-recipes.yml
new file mode 100644
index 000000000..980802e9c
--- /dev/null
+++ b/headless-services/commons/commons-rewrite-test/src/main/resources/META-INF/rewrite/test-recipes.yml
@@ -0,0 +1,11 @@
+########################################################################################################################
+# Spring Data 3.0 io.micrometer.core.instrument.binder -> io.micrometer.binder
+type: specs.openrewrite.org/v1beta/recipe
+name: rewrite.test.jar.oss-package
+displayName: Example with OSS packages from YAML
+description: Switches app to OSS app
+recipeList:
+ - org.openrewrite.java.ChangePackage:
+ oldPackageName: com.example
+ newPackageName: org.example
+ recursive: true
diff --git a/headless-services/commons/commons-rewrite/pom.xml b/headless-services/commons/commons-rewrite/pom.xml
index 6149ea10c..803b727cc 100644
--- a/headless-services/commons/commons-rewrite/pom.xml
+++ b/headless-services/commons/commons-rewrite/pom.xml
@@ -22,7 +22,18 @@
commons-language-server
${project.version}
+
+ org.springframework.ide.vscode
+ commons-java
+ ${project.version}
+
+
+
+ io.github.classgraph
+ classgraph
+ 4.8.149
+
org.openrewrite
rewrite-properties
diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/CodeActionRepository.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/CodeActionRepository.java
new file mode 100644
index 000000000..81e4ea325
--- /dev/null
+++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/CodeActionRepository.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.commons.rewrite.config;
+
+import java.util.List;
+
+public abstract class CodeActionRepository {
+
+ public abstract List getCodeActionDescriptors();
+
+ public abstract List getProblemDescriptors();
+
+}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeCodeActionDescriptor.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeCodeActionDescriptor.java
similarity index 94%
rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeCodeActionDescriptor.java
rename to headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeCodeActionDescriptor.java
index 856076292..0aa97f4d1 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeCodeActionDescriptor.java
+++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeCodeActionDescriptor.java
@@ -8,7 +8,7 @@
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
-package org.springframework.ide.vscode.boot.java.rewrite;
+package org.springframework.ide.vscode.commons.rewrite.config;
import org.openrewrite.ExecutionContext;
import org.openrewrite.java.JavaVisitor;
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeScope.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeScope.java
similarity index 90%
rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeScope.java
rename to headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeScope.java
index 9c3530365..0c0835a53 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeScope.java
+++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeScope.java
@@ -8,7 +8,7 @@
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
-package org.springframework.ide.vscode.boot.java.rewrite;
+package org.springframework.ide.vscode.commons.rewrite.config;
public enum RecipeScope {
NODE,
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/RecipeSpringJavaProblemDescriptor.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeSpringJavaProblemDescriptor.java
similarity index 82%
rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/RecipeSpringJavaProblemDescriptor.java
rename to headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeSpringJavaProblemDescriptor.java
index 57075c8cb..6d9801639 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/RecipeSpringJavaProblemDescriptor.java
+++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeSpringJavaProblemDescriptor.java
@@ -8,9 +8,8 @@
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
-package org.springframework.ide.vscode.boot.java.rewrite.reconcile;
+package org.springframework.ide.vscode.commons.rewrite.config;
-import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
public interface RecipeSpringJavaProblemDescriptor extends RecipeCodeActionDescriptor {
diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsClasspathScanningLoader.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsClasspathScanningLoader.java
new file mode 100644
index 000000000..d60b0f402
--- /dev/null
+++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsClasspathScanningLoader.java
@@ -0,0 +1,209 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.commons.rewrite.config;
+
+import static org.openrewrite.internal.RecipeIntrospectionUtils.constructRecipe;
+import static org.openrewrite.internal.RecipeIntrospectionUtils.recipeDescriptorFromRecipe;
+
+import java.lang.reflect.Constructor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+
+import javax.annotation.Nullable;
+
+import org.openrewrite.Recipe;
+import org.openrewrite.config.CategoryDescriptor;
+import org.openrewrite.config.DeclarativeRecipe;
+import org.openrewrite.config.RecipeDescriptor;
+import org.openrewrite.config.RecipeExample;
+import org.openrewrite.config.ResourceLoader;
+import org.openrewrite.config.YamlResourceLoader;
+import org.openrewrite.internal.RecipeIntrospectionUtils;
+import org.openrewrite.style.NamedStyles;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ScanResult;
+
+public class StsClasspathScanningLoader implements ResourceLoader, StsResourceLoader {
+
+ private final List recipes = new ArrayList<>();
+ private final List styles = new ArrayList<>();
+
+ private final List recipeDescriptors = new ArrayList<>();
+ private final List categoryDescriptors = new ArrayList<>();
+ private final List recipeExamples = new ArrayList<>();
+
+ private final List codeActionRepos = new ArrayList<>();
+
+ public StsClasspathScanningLoader(Path p, Properties properties, ClassLoader classLoader) {
+ if (Files.isDirectory(p)) {
+ String dir = p.toString();
+
+ scanClasses(new ClassGraph()
+ .acceptPaths(dir)
+ .ignoreParentClassLoaders()
+ .overrideClassLoaders(classLoader), classLoader);
+
+ scanYaml(new ClassGraph()
+ .acceptPaths(dir)
+ .ignoreParentClassLoaders()
+ .overrideClassLoaders(classLoader)
+ .acceptPaths("META-INF/rewrite"), properties, classLoader);
+
+ } else {
+ String jarName = p.toFile().getName();
+
+ scanClasses(new ClassGraph()
+ .acceptJars(jarName)
+ .ignoreParentClassLoaders()
+ .overrideClassLoaders(classLoader), classLoader);
+
+ scanYaml(new ClassGraph()
+ .acceptJars(jarName)
+ .ignoreParentClassLoaders()
+ .overrideClassLoaders(classLoader)
+ .acceptPaths("META-INF/rewrite"), properties, classLoader);
+ }
+
+ }
+
+ public StsClasspathScanningLoader(Properties properties, String[] acceptPackages) {
+ scanClasses(new ClassGraph().acceptPackages(acceptPackages), getClass().getClassLoader());
+ scanYaml(new ClassGraph().acceptPaths("META-INF/rewrite"), properties, null);
+ }
+
+ /**
+ * Construct a ClasspathScanningLoader scans the provided classload for recipes
+ *
+ * @param properties Yaml placeholder properties
+ * @param classLoader Limit scan to classes loadable by this classloader
+ */
+ public StsClasspathScanningLoader(Properties properties, ClassLoader classLoader) {
+ scanClasses(new ClassGraph()
+ .ignoreParentClassLoaders()
+ .overrideClassLoaders(classLoader), classLoader);
+
+ scanYaml(new ClassGraph()
+ .ignoreParentClassLoaders()
+ .overrideClassLoaders(classLoader)
+ .acceptPaths("META-INF/rewrite"), properties, classLoader);
+ }
+
+ /**
+ * This must be called _after_ scanClasses or the descriptors of declarative recipes will be missing any
+ * non-declarative recipes they depend on that would be discovered by scanClasses
+ */
+ private void scanYaml(ClassGraph classGraph, Properties properties, @Nullable ClassLoader classLoader) {
+ try (ScanResult scanResult = classGraph.enableMemoryMapping().scan()) {
+ List yamlResourceLoaders = new ArrayList<>();
+
+ scanResult.getResourcesWithExtension("yml").forEachInputStreamIgnoringIOException((res, input) -> {
+ yamlResourceLoaders.add(new YamlResourceLoader(input, res.getURI(), properties, classLoader));
+ });
+ // Extract in two passes so that the full list of recipes from all sources are known when computing recipe descriptors
+ // Otherwise recipes which include recipes from other sources in their recipeList will have incomplete descriptors
+ for(YamlResourceLoader resourceLoader : yamlResourceLoaders) {
+ recipes.addAll(resourceLoader.listRecipes());
+ categoryDescriptors.addAll(resourceLoader.listCategoryDescriptors());
+ styles.addAll(resourceLoader.listStyles());
+ recipeExamples.addAll(resourceLoader.listRecipeExamples());
+ }
+ for(YamlResourceLoader resourceLoader : yamlResourceLoaders) {
+ recipeDescriptors.addAll(resourceLoader.listRecipeDescriptors(recipes));
+ }
+ }
+ }
+
+ private void scanClasses(ClassGraph classGraph, ClassLoader classLoader) {
+ try (ScanResult result = classGraph
+ .ignoreClassVisibility()
+ .overrideClassLoaders(classLoader)
+ .scan()) {
+
+ for (ClassInfo classInfo : result.getSubclasses(Recipe.class.getName())) {
+ Class> recipeClass = classInfo.loadClass();
+ if (recipeClass.getName().equals(DeclarativeRecipe.class.getName()) || recipeClass.getEnclosingClass() != null) {
+ continue;
+ }
+ try {
+ Recipe recipe = constructRecipe(recipeClass);
+ recipeDescriptors.add(recipeDescriptorFromRecipe(recipe));
+ recipes.add(recipe);
+ } catch (Exception e) {
+// logger.warn("Unable to configure {}", recipeClass.getName(), e);
+ }
+ }
+ for (ClassInfo classInfo : result.getSubclasses(NamedStyles.class.getName())) {
+ Class> styleClass = classInfo.loadClass();
+ try {
+ Constructor> constructor = RecipeIntrospectionUtils.getZeroArgsConstructor(styleClass);
+ if(constructor != null) {
+ constructor.setAccessible(true);
+ styles.add((NamedStyles) constructor.newInstance());
+ }
+ } catch (Exception e) {
+// logger.warn("Unable to configure {}", styleClass.getName(), e);
+ }
+ }
+
+ for (ClassInfo classInfo : result.getSubclasses(CodeActionRepository.class.getName())) {
+ Class> codeActionRepoClass = classInfo.loadClass();
+ Constructor> primaryConstructor = RecipeIntrospectionUtils.getZeroArgsConstructor(codeActionRepoClass);
+ if (primaryConstructor == null) {
+ //TODO: error!!!
+ } else {
+ try {
+ CodeActionRepository repo = (CodeActionRepository) primaryConstructor.newInstance();
+ codeActionRepos.add(repo);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+ }
+ }
+
+ @Override
+ public Collection listRecipes() {
+ return recipes;
+ }
+
+ @Override
+ public Collection listRecipeDescriptors() {
+ return recipeDescriptors;
+ }
+
+ @Override
+ public Collection listCategoryDescriptors() {
+ return categoryDescriptors;
+ }
+
+ @Override
+ public Collection listStyles() {
+ return styles;
+ }
+
+ @Override
+ public Collection listRecipeExamples() {
+ return recipeExamples;
+ }
+
+ public List listCodeActionDescriptorsRepositories() {
+ return codeActionRepos;
+ }
+
+}
diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsEnvironment.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsEnvironment.java
new file mode 100644
index 000000000..40f55a572
--- /dev/null
+++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsEnvironment.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.commons.rewrite.config;
+
+import java.lang.reflect.Field;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.openrewrite.config.Environment;
+import org.openrewrite.config.ResourceLoader;
+
+public class StsEnvironment extends Environment {
+
+ final private Supplier> codeActionRepos;
+
+ public StsEnvironment(Collection extends ResourceLoader> resourceLoaders) {
+ super(resourceLoaders);
+ codeActionRepos = () -> resourceLoaders.stream().filter(StsResourceLoader.class::isInstance).map(StsResourceLoader.class::cast).flatMap(l -> l.listCodeActionDescriptorsRepositories().stream());
+ }
+
+ public static class Builder extends Environment.Builder {
+
+ final private Properties props;
+
+ public Builder(Properties properties) {
+ super(properties);
+ this.props = properties;
+ }
+
+ @Override
+ public org.openrewrite.config.Environment.Builder scanRuntimeClasspath(String... acceptPackages) {
+ return load(new StsClasspathScanningLoader(props, acceptPackages));
+ }
+
+ @Override
+ public org.openrewrite.config.Environment.Builder scanClassLoader(ClassLoader classLoader) {
+ return load(new StsClasspathScanningLoader(props, classLoader));
+ }
+
+ @Override
+ public org.openrewrite.config.Environment.Builder scanJar(Path jar, ClassLoader classLoader) {
+ return load(new StsClasspathScanningLoader(jar, props, classLoader));
+ }
+
+ public org.openrewrite.config.Environment.Builder scanPath(Path dir, ClassLoader classLoader) {
+ return load(new StsClasspathScanningLoader(dir, props, classLoader));
+ }
+
+ @SuppressWarnings("unchecked")
+ public StsEnvironment build() {
+ try {
+ Field f = Environment.Builder.class.getDeclaredField("resourceLoaders");
+ f.setAccessible(true);
+ return new StsEnvironment((Collection) f.get(this));
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+
+ }
+
+ public List listCodeActionDescriptors() {
+ return codeActionRepos.get().flatMap(r -> r.getCodeActionDescriptors().stream()).collect(Collectors.toList());
+ }
+
+ public List listProblemDescriptors() {
+ return codeActionRepos.get().flatMap(r -> r.getProblemDescriptors().stream()).collect(Collectors.toList());
+ }
+
+ public static Builder builder() {
+ return new Builder(new Properties());
+ }
+
+}
diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsResourceLoader.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsResourceLoader.java
new file mode 100644
index 000000000..135aaad1e
--- /dev/null
+++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsResourceLoader.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.commons.rewrite.config;
+
+import java.util.List;
+
+import org.openrewrite.config.ResourceLoader;
+
+public interface StsResourceLoader extends ResourceLoader {
+
+ List listCodeActionDescriptorsRepositories();
+
+
+}
diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java
index d98a5e7c9..0c1eca018 100644
--- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java
+++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java
@@ -6,12 +6,13 @@ import java.util.Objects;
import java.util.UUID;
import org.openrewrite.marker.Marker;
+import org.openrewrite.marker.Range;
public class FixAssistMarker implements Marker {
private UUID id;
- private UUID scope;
+ private Range scope;
private String recipeId;
@@ -34,12 +35,12 @@ public class FixAssistMarker implements Marker {
return this;
}
- public FixAssistMarker withScope(UUID scope) {
+ public FixAssistMarker withScope(Range scope) {
this.scope = scope;
return this;
}
- public UUID getScope() {
+ public Range getScope() {
return scope;
}
diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java
index fe3960d34..b38b6b089 100644
--- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java
+++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java
@@ -329,15 +329,18 @@ public class ORAstUtils {
@Override
public J visit(Tree tree, ExecutionContext ctx) {
- J t = super.visit(tree, ctx);
- if (condition.test(t)) {
- makeVisitorNonTopLevel(visitor);
- t = visitor.visit(t, ctx, getCursor());
- for (TreeVisitor v : getAfterVisitors(visitor)) {
- doAfterVisit(v);
- }
+ if (tree instanceof J) {
+ J t = (J) tree;
+ if (condition.test(t)) {
+ makeVisitorNonTopLevel(visitor);
+ t = visitor.visit(t, ctx, getCursor());
+ for (TreeVisitor v : getAfterVisitors(visitor)) {
+ doAfterVisit(v);
+ }
+ return t;
+ }
}
- return t;
+ return super.visit(tree, ctx);
}
};
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java
index 310a2fb6d..fd562fcf3 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.app;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
import org.slf4j.Logger;
@@ -162,7 +163,15 @@ public class BootJavaConfig implements InitializingBean {
public void afterPropertiesSet() throws Exception {
workspace.onDidChangeConfiguraton(this::handleConfigurationChange);
}
+
+ public Set getRecipeDirectories() {
+ return settings.getStringSet("boot-java", "rewrite", "scan-directories");
+ }
+ public Set getRecipeFiles() {
+ return settings.getStringSet("boot-java", "rewrite", "scan-files");
+ }
+
public Settings getRawSettings() {
return settings;
}
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 b08190e80..ba1d3a087 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
@@ -317,7 +317,7 @@ public class BootLanguageServerBootApp {
};
}
- @Bean RewriteRecipeRepository rewriteRecipesRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder) {
- return new RewriteRecipeRepository(server, projectFinder);
+ @Bean RewriteRecipeRepository rewriteRecipesRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder, BootJavaConfig config) {
+ return new RewriteRecipeRepository(server, projectFinder, config);
}
}
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 b92803e06..d06dc3760 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
@@ -32,6 +32,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.SymbolCache;
import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider;
@@ -87,6 +88,7 @@ public class BootLanguageServerInitializer implements InitializingBean {
@Autowired SpringProjectsValidations springProjectsValidations;
@Autowired private JavaProjectFinder projectFinder;
@Autowired private LanguageServerProperties configProps;
+ @Autowired(required = false) private RewriteRecipeRepository recipesRepo;
@Qualifier("adHocProperties") @Autowired ProjectBasedPropertyIndexProvider adHocProperties;
@@ -170,21 +172,27 @@ public class BootLanguageServerInitializer implements InitializingBean {
components.getCodeActionProvider().ifPresent(documents::onCodeAction);
- config.addListener(evt -> {
- components.getReconcileEngine().ifPresent(reconciler -> {
- log.info("A configuration changed, triggering reconcile on all open documents");
- for (TextDocument doc : server.getTextDocumentService().getAll()) {
- server.validateWith(doc.getId(), reconciler);
- }
- params.projectFinder.all().forEach(p -> validateProject(p, reconciler));
- });
- });
+ config.addListener(evt -> reconcile());
+
+ if (recipesRepo != null) {
+ recipesRepo.onRecipesLoaded(v -> reconcile());
+ }
addSpringProjectsVersionValidation(params);
server.getWorkspaceService().getFileObserver().onFilesChanged(FILES_TO_WATCH_GLOB, this::handleFiles);
server.getWorkspaceService().getFileObserver().onFilesCreated(FILES_TO_WATCH_GLOB, this::handleFiles);
}
+
+ private void reconcile() {
+ components.getReconcileEngine().ifPresent(reconciler -> {
+ log.info("A configuration changed, triggering reconcile on all open documents");
+ for (TextDocument doc : server.getTextDocumentService().getAll()) {
+ server.validateWith(doc.getId(), reconciler);
+ }
+ params.projectFinder.all().forEach(p -> validateProject(p, reconciler));
+ });
+ }
public CompositeLanguageServerComponents getComponents() {
Assert.notNull(components, "Not yet initialized, can't get components yet.");
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java
index e18b83c21..797bf85e8 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java
@@ -27,9 +27,6 @@ public class RewriteConfig implements InitializingBean {
@Autowired
private SimpleLanguageServer server;
- @Autowired
- private RewriteRecipeRepository recipeRepo;
-
@Autowired
private RewriteRefactorings rewriteRefactorings;
@@ -41,15 +38,7 @@ public class RewriteConfig implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
QuickfixRegistry registry = server.getQuickfixRegistry();
-
- recipeRepo.loaded.thenAccept(v ->
- recipeRepo.getProblemRecipeDescriptors().forEach(d -> {
- if (recipeRepo.getRecipe(d.getRecipeId()).isPresent()) {
- registry.register(d.getRecipeId(), rewriteRefactorings);
- }
- })
- );
-
+ registry.register(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX, rewriteRefactorings);
}
}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/BootCodeActionRepository.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/BootCodeActionRepository.java
new file mode 100644
index 000000000..87c982141
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/BootCodeActionRepository.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.rewrite;
+
+import java.util.List;
+
+import org.springframework.ide.vscode.boot.java.rewrite.codeaction.AutowiredFieldIntoConstructorParameterCodeAction;
+import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNotPublicCodeAction;
+import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMappingAnnotationCodeAction;
+import org.springframework.ide.vscode.boot.java.rewrite.codeaction.UnnecessarySpringExtensionCodeAction;
+import org.springframework.ide.vscode.boot.java.rewrite.reconcile.BeanMethodNotPublicProblem;
+import org.springframework.ide.vscode.boot.java.rewrite.reconcile.NoAutowiredOnConstructorProblem;
+import org.springframework.ide.vscode.boot.java.rewrite.reconcile.PreciseBeanTypeProblem;
+import org.springframework.ide.vscode.boot.java.rewrite.reconcile.UnnecessarySpringExtensionProblem;
+import org.springframework.ide.vscode.commons.rewrite.config.CodeActionRepository;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor;
+
+public class BootCodeActionRepository extends CodeActionRepository {
+
+ @Override
+ public List getCodeActionDescriptors() {
+ return List.of(
+ new AutowiredFieldIntoConstructorParameterCodeAction(),
+ new BeanMethodsNotPublicCodeAction(),
+ new NoRequestMappingAnnotationCodeAction(),
+ new UnnecessarySpringExtensionCodeAction()
+ );
+ }
+
+ @Override
+ public List getProblemDescriptors() {
+ return List.of(
+ new BeanMethodNotPublicProblem(),
+ new NoAutowiredOnConstructorProblem(),
+ new UnnecessarySpringExtensionProblem(),
+ new PreciseBeanTypeProblem()
+ );
+ }
+
+}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java
index d3a879269..5bfa293c3 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java
@@ -37,10 +37,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
-import org.springframework.ide.vscode.boot.java.rewrite.reconcile.RecipeSpringJavaProblemDescriptor;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
import org.springframework.ide.vscode.commons.languageserver.util.LspClient.Client;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor;
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.IRegion;
@@ -99,9 +101,6 @@ public class RewriteCodeActionHandler implements JavaCodeActionHandler {
try {
- // Wait for recipe repo to load if not loaded - should be loaded by the time we get here.
- recipeRepo.loaded.get();
-
List descriptors = recipeRepo.getCodeActionRecipeDescriptors().stream()
// If Recipe not present - don't show quick assist as it won't be handled without the Rewrite recipe present
.filter(d -> recipeRepo.getRecipe(d.getRecipeId()).isPresent())
@@ -177,7 +176,7 @@ public class RewriteCodeActionHandler implements JavaCodeActionHandler {
m.getRecipeId(),
doc.getUri(),
s,
- m.getScope() == null ? null : m.getScope().toString(),
+ m.getScope() == null ? null : m.getScope(),
m.getParameters() == null ? Collections.emptyMap() : m.getParameters()
));
return ca;
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 280b75ab1..657e083bb 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
@@ -11,6 +11,10 @@
package org.springframework.ide.vscode.boot.java.rewrite;
import java.io.File;
+import java.io.FileInputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
@@ -20,9 +24,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -40,31 +46,27 @@ import org.openrewrite.Result;
import org.openrewrite.SourceFile;
import org.openrewrite.TreeVisitor;
import org.openrewrite.Validated;
-import org.openrewrite.config.Environment;
import org.openrewrite.config.RecipeDescriptor;
+import org.openrewrite.config.YamlResourceLoader;
import org.openrewrite.internal.RecipeIntrospectionUtils;
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.ide.vscode.boot.java.rewrite.codeaction.AutowiredFieldIntoConstructorParameterCodeAction;
-import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNotPublicCodeAction;
-import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMappingAnnotationCodeAction;
-import org.springframework.ide.vscode.boot.java.rewrite.codeaction.UnnecessarySpringExtensionCodeAction;
-import org.springframework.ide.vscode.boot.java.rewrite.reconcile.BeanMethodNotPublicProblem;
-import org.springframework.ide.vscode.boot.java.rewrite.reconcile.PreciseBeanTypeProblem;
-import org.springframework.ide.vscode.boot.java.rewrite.reconcile.NoAutowiredOnConstructorProblem;
-import org.springframework.ide.vscode.boot.java.rewrite.reconcile.RecipeSpringJavaProblemDescriptor;
-import org.springframework.ide.vscode.boot.java.rewrite.reconcile.UnnecessarySpringExtensionProblem;
+import org.springframework.ide.vscode.boot.app.BootJavaConfig;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
+import org.springframework.ide.vscode.commons.languageserver.util.ListenerList;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
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.RecipeCodeActionDescriptor;
+import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor;
+import org.springframework.ide.vscode.commons.rewrite.config.StsEnvironment;
import org.springframework.ide.vscode.commons.rewrite.maven.MavenProjectParser;
import com.google.common.collect.ImmutableList;
@@ -76,6 +78,9 @@ import com.google.gson.JsonElement;
public class RewriteRecipeRepository {
+ private static final String CMD_REWRITE_RELOAD = "sts/rewrite/reload";
+ private static final String CMD_REWRITE_EXECUTE = "sts/rewrite/execute";
+ private static final String CMD_REWRITE_LIST = "sts/rewrite/list";
private static final Logger log = LoggerFactory.getLogger(RewriteRecipeRepository.class);
private static final String WORKSPACE_EXECUTE_COMMAND = "workspace/executeCommand";
@@ -84,11 +89,21 @@ public class RewriteRecipeRepository {
final private SimpleLanguageServer server;
final private Map recipes;
+
final private List globalCommandRecipes;
final private JavaProjectFinder projectFinder;
- final public CompletableFuture loaded;
+ final private List codeActionDescriptors;
+
+ final private List javaProblemDescriptors;
+
+ final private ListenerList loadListeners;
+
+ private CompletableFuture loaded;
+
+ private Set scanFiles = Collections.emptySet();
+ private Set scanDirs = Collections.emptySet();
static final Set TOP_LEVEL_RECIPES = Set.of(
"org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration",
@@ -104,40 +119,62 @@ public class RewriteRecipeRepository {
.registerTypeAdapter(Duration.class, new DurationTypeConverter())
.create();
- private List codeActionDescriptors = List.of(
- new AutowiredFieldIntoConstructorParameterCodeAction(),
- new BeanMethodsNotPublicCodeAction(),
- new NoRequestMappingAnnotationCodeAction(),
- new UnnecessarySpringExtensionCodeAction()
- );
-
- private List javaProblemDescriptors = List.of(
- new BeanMethodNotPublicProblem(),
- new NoAutowiredOnConstructorProblem(),
- new UnnecessarySpringExtensionProblem(),
- new PreciseBeanTypeProblem()
- );
-
- public RewriteRecipeRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder) {
+ public RewriteRecipeRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder, BootJavaConfig config) {
this.server = server;
this.projectFinder = projectFinder;
this.recipes = new HashMap<>();
this.globalCommandRecipes = new ArrayList<>();
- this.loaded = CompletableFuture.runAsync(this::loadRecipes);
+ this.codeActionDescriptors = new ArrayList<>();
+ this.javaProblemDescriptors = new ArrayList<>();
+ this.loadListeners = new ListenerList<>();
+
+ server.doOnInitialized(() -> {
+ this.scanDirs = config.getRecipeDirectories();
+ this.scanFiles = config.getRecipeFiles();
+ load().thenAccept(v -> registerCommands());
+ config.addListener(l -> {
+ if (!scanDirs.equals(config.getRecipeDirectories())
+ || !scanFiles.equals(config.getRecipeFiles())) {
+ // Eclipse client sends one event for init and the other config changed event due to remote app value expr listener.
+ // 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();
+ load();
+ }
+ });
+ });
}
- private void loadRecipes() {
+ public CompletableFuture load() {
+ return this.loaded = CompletableFuture.runAsync(() -> {
+ clearRecipes();
+ loadRecipes();
+ loadListeners.fire(null);
+ });
+ }
+
+ private synchronized void clearRecipes() {
+ recipes.clear();
+ globalCommandRecipes.clear();
+ codeActionDescriptors.clear();
+ javaProblemDescriptors.clear();
+ }
+
+ private synchronized void loadRecipes() {
try {
server.getProgressService().progressBegin(RECIPES_LOADING_PROGRESS, "Loading Rewrite Recipes", null);
log.info("Loading Rewrite Recipes...");
- for (Recipe r : Environment.builder().scanRuntimeClasspath().build().listRecipes()) {
+ StsEnvironment env = createRewriteEnvironment();
+ for (Recipe r : env.listRecipes()) {
if (r.getName() != null) {
if (recipes.containsKey(r.getName())) {
log.error("Duplicate ids: '" + r.getName() + "'");
}
recipes.put(r.getName(), r);
- if (TOP_LEVEL_RECIPES.contains(r.getName())) {
+ if (TOP_LEVEL_RECIPES.contains(r.getName()) || r.getName().startsWith("rewrite.test.")
+ || r.getName().startsWith("org.rewrite.java.security")
+ || r.getName().startsWith("org.springframework.rewrite.test")) {
Validated validation = Validated.invalid(null, null, null);
try {
validation = r.validate();
@@ -150,14 +187,47 @@ public class RewriteRecipeRepository {
}
}
}
+ javaProblemDescriptors.addAll(env.listProblemDescriptors());
+ codeActionDescriptors.addAll(env.listCodeActionDescriptors());
log.info("Done loading Rewrite Recipes");
- server.doOnInitialized(() -> registerCommands());
} catch (Throwable t) {
- server.getProgressService().progressDone(RECIPES_LOADING_PROGRESS);
log.error("", t);
+ } finally {
+ server.getProgressService().progressDone(RECIPES_LOADING_PROGRESS);
}
}
+ private StsEnvironment createRewriteEnvironment() {
+ StsEnvironment.Builder builder = (StsEnvironment.Builder) StsEnvironment.builder().scanRuntimeClasspath();
+ for (String p : scanFiles) {
+ try {
+ Path f = Path.of(p);
+ String pathStr = f.toString();
+ if (pathStr.endsWith(".jar")) {
+ URLClassLoader classLoader = new URLClassLoader(new URL[] { f.toUri().toURL() },
+ getClass().getClassLoader());
+ builder.scanJar(f, classLoader);
+ } else if (pathStr.endsWith(".yml") || pathStr.endsWith(".yaml")) {
+ builder.load(new YamlResourceLoader(new FileInputStream(f.toFile()), f.toUri(), new Properties()));
+ }
+ } catch (Exception e) {
+ log.error("Skipping folder " + p, e);
+ }
+ }
+ for (String p : scanDirs) {
+ try {
+ Path d = Path.of(p);
+ if (Files.isDirectory(d)) {
+ URLClassLoader classLoader = new URLClassLoader(new URL[] { d.toUri().toURL()}, getClass().getClassLoader());
+ builder.scanPath(d, classLoader);
+ }
+ } catch (Exception e) {
+ log.error("Skipping folder " + p, e);
+ }
+ }
+ return (StsEnvironment) builder.build();
+ }
+
public Optional getRecipe(String name) {
return Optional.ofNullable(recipes.get(name));
}
@@ -223,7 +293,7 @@ public class RewriteRecipeRepository {
Builder