Minimize size of messages for fetching and executing recipes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
|
||||
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||
<classpathentry kind="src" path="src/"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
|
||||
org.eclipse.jdt.core.compiler.compliance=11
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
|
||||
org.eclipse.jdt.core.compiler.compliance=17
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||
org.eclipse.jdt.core.compiler.release=enabled
|
||||
org.eclipse.jdt.core.compiler.source=11
|
||||
org.eclipse.jdt.core.compiler.source=17
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 VMware, Inc.
|
||||
* Copyright (c) 2022, 2023 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -10,12 +10,16 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.tooling.boot.ls.commands;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
class RecipeDescriptor {
|
||||
final class RecipeDescriptor {
|
||||
|
||||
enum CheckedState {
|
||||
UNCHECKED,
|
||||
CHECKED,
|
||||
GRAYED
|
||||
}
|
||||
|
||||
String name;
|
||||
|
||||
@@ -25,45 +29,29 @@ class RecipeDescriptor {
|
||||
|
||||
Set<String> tags;
|
||||
|
||||
Duration estimatedEffortPerOccurrence;
|
||||
|
||||
List<OptionDescriptor> options;
|
||||
|
||||
List<String> languages;
|
||||
|
||||
List<RecipeDescriptor> recipeList;
|
||||
|
||||
URI source;
|
||||
|
||||
RecipeDescriptor getCopyWithoutSubRecipes() {
|
||||
RecipeDescriptor copy = new RecipeDescriptor();
|
||||
copy.name = name;
|
||||
copy.displayName = displayName;
|
||||
copy.description = description;
|
||||
copy.tags = tags;
|
||||
copy.estimatedEffortPerOccurrence = estimatedEffortPerOccurrence;
|
||||
copy.options = options;
|
||||
copy.languages = languages;
|
||||
copy.source = source;
|
||||
return copy;
|
||||
}
|
||||
|
||||
static class OptionDescriptor {
|
||||
boolean hasSubRecipes = false;
|
||||
|
||||
RecipeDescriptor parent;
|
||||
|
||||
CheckedState checked = CheckedState.UNCHECKED;
|
||||
|
||||
String name;
|
||||
|
||||
String type;
|
||||
|
||||
String displayName;
|
||||
|
||||
String description;
|
||||
|
||||
String example;
|
||||
|
||||
List<String> valid;
|
||||
|
||||
boolean required;
|
||||
|
||||
Object value;
|
||||
}
|
||||
record OptionDescriptor(
|
||||
String name,
|
||||
String type,
|
||||
String displayName,
|
||||
String description,
|
||||
String example,
|
||||
List<String> valid,
|
||||
boolean required,
|
||||
Object value
|
||||
) {}
|
||||
|
||||
record RecipeSelection(boolean selected, String id, RecipeSelection[] subselection) {}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 VMware, Inc.
|
||||
* Copyright (c) 2022, 2023 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -10,55 +10,48 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.tooling.boot.ls.commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.lsp4j.ExecuteCommandParams;
|
||||
import org.eclipse.lsp4j.services.WorkspaceService;
|
||||
import org.springframework.tooling.boot.ls.commands.RecipeDescriptor.CheckedState;
|
||||
import org.springframework.tooling.boot.ls.commands.RecipeDescriptor.RecipeSelection;
|
||||
|
||||
|
||||
public class RecipeTreeModel {
|
||||
|
||||
public enum CheckedState {
|
||||
UNCHECKED,
|
||||
CHECKED,
|
||||
GRAYED
|
||||
}
|
||||
private static final String REWRITE_REFACTORINGS_LIST = "sts/rewrite/list";
|
||||
private static final String REWRITE_REFACTORINGS_SUBLIST = "sts/rewrite/sublist";
|
||||
|
||||
private RecipeDescriptor[] recipeDescriptors;
|
||||
private Map<RecipeDescriptor, RecipeDescriptor> parentMap = new IdentityHashMap<>();
|
||||
private Map<RecipeDescriptor, CheckedState> checkedMap = new IdentityHashMap<>();
|
||||
|
||||
RecipeTreeModel(RecipeDescriptor[] recipeDescriptors) {
|
||||
this.recipeDescriptors = recipeDescriptors;
|
||||
for (RecipeDescriptor d : recipeDescriptors) {
|
||||
initParentMap(d);
|
||||
}
|
||||
}
|
||||
|
||||
private void initParentMap(RecipeDescriptor d) {
|
||||
if (d.recipeList != null) {
|
||||
for (RecipeDescriptor dc : d.recipeList) {
|
||||
parentMap.put(dc, d);
|
||||
initParentMap(dc);
|
||||
}
|
||||
}
|
||||
final private WorkspaceService workspaceService;
|
||||
final private String recipeFilter;
|
||||
|
||||
RecipeTreeModel(WorkspaceService workspaceService, String recipeFilter) {
|
||||
this.workspaceService = workspaceService;
|
||||
this.recipeFilter = recipeFilter;
|
||||
}
|
||||
|
||||
public void check(RecipeDescriptor d) {
|
||||
if (simpleCheck(d)) {
|
||||
inferCheckedStateFromChildren(parentMap.get(d));
|
||||
inferCheckedStateFromChildren(d.parent);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean simpleCheck(RecipeDescriptor d) {
|
||||
if (checkedMap.get(d) != CheckedState.CHECKED) {
|
||||
checkedMap.put(d, CheckedState.CHECKED);
|
||||
for (RecipeDescriptor dc : d.recipeList) {
|
||||
simpleCheck(dc);
|
||||
if (d.checked != CheckedState.CHECKED) {
|
||||
d.checked = CheckedState.CHECKED;
|
||||
if (d.recipeList != null) {
|
||||
for (RecipeDescriptor dc : d.recipeList) {
|
||||
simpleCheck(dc);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -67,32 +60,29 @@ public class RecipeTreeModel {
|
||||
|
||||
public void uncheck(RecipeDescriptor d) {
|
||||
if (simpleUncheck(d)) {
|
||||
inferCheckedStateFromChildren(parentMap.get(d));
|
||||
inferCheckedStateFromChildren(d.parent);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean simpleUncheck(RecipeDescriptor d) {
|
||||
if (checkedMap.get(d) != CheckedState.UNCHECKED) {
|
||||
checkedMap.put(d, CheckedState.UNCHECKED);
|
||||
for (RecipeDescriptor dc : d.recipeList) {
|
||||
simpleUncheck(dc);
|
||||
if (d.checked != CheckedState.UNCHECKED) {
|
||||
d.checked = CheckedState.UNCHECKED;
|
||||
if (d.recipeList != null) {
|
||||
for (RecipeDescriptor dc : d.recipeList) {
|
||||
simpleUncheck(dc);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public CheckedState getCheckedState(RecipeDescriptor d) {
|
||||
CheckedState state = checkedMap.get(d);
|
||||
return state == null ? CheckedState.UNCHECKED : state;
|
||||
}
|
||||
|
||||
private void inferCheckedStateFromChildren(RecipeDescriptor d) {
|
||||
if (d != null && d.recipeList != null) {
|
||||
boolean all = true;
|
||||
boolean none = true;
|
||||
for (RecipeDescriptor child : d.recipeList) {
|
||||
CheckedState childState = getCheckedState(child);
|
||||
CheckedState childState = child.checked;
|
||||
if (childState == CheckedState.UNCHECKED) {
|
||||
all = false;
|
||||
} else {
|
||||
@@ -105,9 +95,9 @@ public class RecipeTreeModel {
|
||||
} else if (none) {
|
||||
inferredState = CheckedState.UNCHECKED;
|
||||
}
|
||||
if (getCheckedState(d) != inferredState) {
|
||||
checkedMap.put(d, inferredState);
|
||||
inferCheckedStateFromChildren(parentMap.get(d));
|
||||
if (d.checked != inferredState) {
|
||||
d.checked = inferredState;
|
||||
inferCheckedStateFromChildren(d.parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,32 +106,70 @@ public class RecipeTreeModel {
|
||||
return recipeDescriptors;
|
||||
}
|
||||
|
||||
public RecipeDescriptor getSelectedRecipeDescriptors() throws CoreException {
|
||||
RecipeDescriptor[] recipes = Arrays.stream(recipeDescriptors).map(this::copySelectedDescriptor).filter(Objects::nonNull).toArray(RecipeDescriptor[]::new);
|
||||
if (recipes.length == 0) {
|
||||
throw new CoreException(Status.error("No recipes selected"));
|
||||
} else if (recipes.length == 1) {
|
||||
return recipes[0];
|
||||
} else {
|
||||
RecipeDescriptor aggregate = new RecipeDescriptor();
|
||||
aggregate.name = recipes.length + " recipes";
|
||||
aggregate.displayName = recipes.length + " recipes";
|
||||
aggregate.description = "Multiple recipes to be applied. Number of recipes " + recipes.length;
|
||||
aggregate.tags = Arrays.stream(recipes).flatMap(r -> r.tags.stream()).collect(Collectors.toSet());
|
||||
aggregate.recipeList = Arrays.asList(recipes);
|
||||
return aggregate;
|
||||
public RecipeSelection[] getRecipeSelection() throws CoreException {
|
||||
List<RecipeSelection> rootSelected = new ArrayList<>();
|
||||
for (int i = 0; i < recipeDescriptors.length; i++) {
|
||||
if (recipeDescriptors[i].checked != CheckedState.UNCHECKED) {
|
||||
rootSelected.add(new RecipeSelection(true, recipeDescriptors[i].name, createRecipeSelection(recipeDescriptors[i])));
|
||||
}
|
||||
}
|
||||
if (rootSelected.isEmpty()) {
|
||||
throw new CoreException(Status.error("No recipes selected"));
|
||||
}
|
||||
return rootSelected.toArray(new RecipeSelection[rootSelected.size()]);
|
||||
}
|
||||
|
||||
private RecipeDescriptor copySelectedDescriptor(RecipeDescriptor d) {
|
||||
if (getCheckedState(d) != CheckedState.UNCHECKED) {
|
||||
RecipeDescriptor copy = d.getCopyWithoutSubRecipes();
|
||||
if (d.recipeList != null) {
|
||||
copy.recipeList = d.recipeList.stream().map(this::copySelectedDescriptor).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
return copy;
|
||||
private RecipeSelection[] createRecipeSelection(RecipeDescriptor d) {
|
||||
if (d.recipeList != null) {
|
||||
return d.recipeList.stream()
|
||||
.map(s -> new RecipeSelection(s.checked != CheckedState.UNCHECKED, s.name, createRecipeSelection(s)))
|
||||
.toArray(RecipeSelection[]::new);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
CompletableFuture<Void> fetchSubrecipes(RecipeDescriptor descriptor) {
|
||||
RecipeDescriptor d = descriptor;
|
||||
LinkedList<Integer> indexPath = new LinkedList<>();
|
||||
for (; d.parent != null; d = d.parent) {
|
||||
indexPath.addFirst(d.parent.recipeList.indexOf(d));
|
||||
}
|
||||
ExecuteCommandParams commandParams = new ExecuteCommandParams();
|
||||
commandParams.setCommand(REWRITE_REFACTORINGS_SUBLIST);
|
||||
commandParams.setArguments(List.of(d.name, indexPath));
|
||||
return workspaceService.executeCommand(commandParams).thenAccept(json -> {
|
||||
RecipeDescriptor[] fetchedDescriptors = RewriteRefactoringsHandler.SERIALIZATION_GSON.fromJson(RewriteRefactoringsHandler.SERIALIZATION_GSON.toJson(json), RecipeDescriptor[].class);
|
||||
for (RecipeDescriptor fd : fetchedDescriptors) {
|
||||
fd.parent = descriptor;
|
||||
fd.checked = descriptor.checked != CheckedState.UNCHECKED ? CheckedState.CHECKED : CheckedState.UNCHECKED;
|
||||
}
|
||||
descriptor.recipeList = Arrays.asList(fetchedDescriptors);
|
||||
});
|
||||
}
|
||||
|
||||
CompletableFuture<Void> fetchRootRecipes() {
|
||||
ExecuteCommandParams commandParams = new ExecuteCommandParams();
|
||||
commandParams.setCommand(REWRITE_REFACTORINGS_LIST);
|
||||
commandParams.setArguments(List.of(recipeFilter));
|
||||
return workspaceService.executeCommand(commandParams).thenAccept(json -> {
|
||||
recipeDescriptors = RewriteRefactoringsHandler.SERIALIZATION_GSON.fromJson(RewriteRefactoringsHandler.SERIALIZATION_GSON.toJson(json), RecipeDescriptor[].class);
|
||||
});
|
||||
}
|
||||
|
||||
String getSelectedRecipeDisplayName() {
|
||||
List<RecipeDescriptor> rootSelected = new ArrayList<>();
|
||||
for (int i = 0; i < recipeDescriptors.length; i++) {
|
||||
if (recipeDescriptors[i].checked != CheckedState.UNCHECKED) {
|
||||
rootSelected.add(recipeDescriptors[i]);
|
||||
}
|
||||
}
|
||||
if (rootSelected.isEmpty()) {
|
||||
return "No Recipes Selected";
|
||||
} else if (rootSelected.size() == 1) {
|
||||
return rootSelected.get(0).displayName;
|
||||
} else {
|
||||
return "%s recipes".formatted(rootSelected.size());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
package org.springframework.tooling.boot.ls.commands;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.commands.AbstractHandler;
|
||||
@@ -37,17 +33,11 @@ import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.eclipse.ui.handlers.HandlerUtil;
|
||||
import org.springframework.tooling.boot.ls.BootLanguageServerPlugin;
|
||||
import org.springframework.tooling.boot.ls.commands.RecipeDescriptor.RecipeSelection;
|
||||
import org.springsource.ide.eclipse.commons.core.CoreUtil;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class RewriteRefactoringsHandler extends AbstractHandler {
|
||||
@@ -58,25 +48,11 @@ public class RewriteRefactoringsHandler extends AbstractHandler {
|
||||
NON_BOOT_UPGRADE
|
||||
}
|
||||
|
||||
private static class DurationTypeConverter implements JsonSerializer<Duration>, JsonDeserializer<Duration> {
|
||||
@Override
|
||||
public JsonElement serialize(Duration src, Type srcType, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.toNanos());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration deserialize(JsonElement json, Type type, JsonDeserializationContext context)
|
||||
throws JsonParseException {
|
||||
return Duration.ofNanos(json.getAsLong());
|
||||
}
|
||||
}
|
||||
|
||||
private static Gson serializationGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Duration.class, new DurationTypeConverter())
|
||||
static final Gson SERIALIZATION_GSON = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
|
||||
private static final String REWRITE_REFACTORINGS_LIST = "sts/rewrite/list";
|
||||
private static final String REWRITE_REFACTORINGS_EXEC = "sts/rewrite/execute";
|
||||
|
||||
private RecipeFilter recipeFilter;
|
||||
@@ -106,41 +82,26 @@ public class RewriteRefactoringsHandler extends AbstractHandler {
|
||||
if (project != null && CoreUtil.promptForProjectSave(project)) {
|
||||
LanguageServerDefinition def = LanguageServersRegistry.getInstance().getDefinition(BootLanguageServerPlugin.BOOT_LS_DEFINITION_ID);
|
||||
Assert.isLegal(def != null, "No definition found for Boot Language Server");
|
||||
|
||||
final String uri = project.getLocationURI().toASCIIString();
|
||||
ExecuteCommandParams commandParams = new ExecuteCommandParams();
|
||||
commandParams.setCommand(REWRITE_REFACTORINGS_LIST);
|
||||
commandParams.setArguments(List.of(uri, recipeFilter.toString()));
|
||||
|
||||
|
||||
try {
|
||||
List<Object> allRewriteRecipesJson = new ArrayList<>();
|
||||
List<Object> syncRecipesJson = Collections.synchronizedList(allRewriteRecipesJson);
|
||||
|
||||
LanguageServers.forProject(project).withPreferredServer(def).computeFirst(ls ->
|
||||
ls.getWorkspaceService().executeCommand(commandParams).thenAccept(or -> {
|
||||
if (or != null) {
|
||||
syncRecipesJson.add(or);
|
||||
}
|
||||
})
|
||||
.thenRun(() -> {
|
||||
allRewriteRecipesJson.stream().filter(List.class::isInstance).map(List.class::cast).findFirst().ifPresent(obj -> {
|
||||
RecipeDescriptor[] descriptors = serializationGson.fromJson(serializationGson.toJson(obj), RecipeDescriptor[].class);
|
||||
LanguageServers.forProject(project).withPreferredServer(def).computeFirst(ls -> {
|
||||
|
||||
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
|
||||
RecipeTreeModel recipesModel = new RecipeTreeModel(descriptors);
|
||||
RecipeTreeModel recipesModel = new RecipeTreeModel(ls.getWorkspaceService(), recipeFilter.toString());
|
||||
int returnCode = new SelectRecipesDialog(Display.getCurrent().getActiveShell(), recipesModel).open();
|
||||
if (returnCode == Window.OK) {
|
||||
try {
|
||||
RecipeDescriptor recipeToApply = recipesModel.getSelectedRecipeDescriptors();
|
||||
final RecipeSelection[] recipeSelection = recipesModel.getRecipeSelection();
|
||||
PlatformUI.getWorkbench().getProgressService().run(true, false, monitor -> {
|
||||
try {
|
||||
monitor.beginTask("Applying recipe '" + recipeToApply.displayName + "'", IProgressMonitor.UNKNOWN);
|
||||
monitor.beginTask("Applying recipe '%s'...".formatted(recipesModel.getSelectedRecipeDisplayName()), IProgressMonitor.UNKNOWN);
|
||||
ExecuteCommandParams cmdParams = new ExecuteCommandParams();
|
||||
cmdParams.setCommand(REWRITE_REFACTORINGS_EXEC);
|
||||
cmdParams.setArguments(List.of(
|
||||
uri,
|
||||
serializationGson.toJsonTree(recipeToApply)
|
||||
SERIALIZATION_GSON.toJsonTree(recipeSelection)
|
||||
));
|
||||
|
||||
ls.getWorkspaceService().executeCommand(cmdParams).get();
|
||||
@@ -157,8 +118,9 @@ public class RewriteRefactoringsHandler extends AbstractHandler {
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
return null;
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new ExecutionException("Failed to apply Rewrite recipe(s)", e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 VMware, Inc.
|
||||
* Copyright (c) 2022, 2023 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -43,7 +43,7 @@ import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.springframework.tooling.boot.ls.commands.RecipeDescriptor.OptionDescriptor;
|
||||
import org.springframework.tooling.boot.ls.commands.RecipeTreeModel.CheckedState;
|
||||
import org.springframework.tooling.boot.ls.commands.RecipeDescriptor.CheckedState;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class SelectRecipesDialog extends StatusDialog {
|
||||
@@ -51,6 +51,7 @@ public class SelectRecipesDialog extends StatusDialog {
|
||||
private static final int MARGIN = 5;
|
||||
private static final String SELECT_REWRITE_RECIPE_S_FROM_THE_LIST = "Select Rewrite Recipe(s) from the list";
|
||||
private static String fgStyleSheet;
|
||||
private static final Object LOADING = new Object();
|
||||
|
||||
private RecipeTreeModel model;
|
||||
|
||||
@@ -76,37 +77,55 @@ public class SelectRecipesDialog extends StatusDialog {
|
||||
|
||||
@Override
|
||||
public Object[] getElements(Object inputElement) {
|
||||
if (inputElement instanceof RecipeTreeModel) {
|
||||
return ((RecipeTreeModel) inputElement).getRecipeDescriptors();
|
||||
if (inputElement instanceof RecipeTreeModel model) {
|
||||
if (model.getRecipeDescriptors() == null) {
|
||||
model.fetchRootRecipes().thenAccept(v -> {
|
||||
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> treeViewer.refresh());
|
||||
});
|
||||
return new Object[] { LOADING };
|
||||
} else {
|
||||
return model.getRecipeDescriptors();
|
||||
}
|
||||
|
||||
}
|
||||
return new Object[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getChildren(Object parentElement) {
|
||||
if (parentElement instanceof RecipeDescriptor) {
|
||||
RecipeDescriptor r = (RecipeDescriptor) parentElement;
|
||||
return r.recipeList.toArray(new RecipeDescriptor[r.recipeList.size()]);
|
||||
if (parentElement instanceof RecipeDescriptor r) {
|
||||
if (r.hasSubRecipes) {
|
||||
if (r.recipeList == null) {
|
||||
model.fetchSubrecipes(r).thenAccept(v -> PlatformUI.getWorkbench().getDisplay().asyncExec(() -> treeViewer.refresh(r)));
|
||||
return new Object[] { LOADING };
|
||||
} else {
|
||||
return r.recipeList.toArray(new RecipeDescriptor[r.recipeList.size()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new RecipeDescriptor[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getParent(Object element) {
|
||||
if (element instanceof RecipeDescriptor r) {
|
||||
return r.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasChildren(Object element) {
|
||||
if (element instanceof RecipeDescriptor) {
|
||||
RecipeDescriptor r = (RecipeDescriptor) element;
|
||||
return r.recipeList != null && !r.recipeList.isEmpty();
|
||||
if (element instanceof RecipeDescriptor r) {
|
||||
return r.hasSubRecipes;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
treeViewer.setLabelProvider(LabelProvider.createTextProvider(input -> {
|
||||
if (input instanceof RecipeDescriptor) {
|
||||
if (input == LOADING) {
|
||||
return "Loading...";
|
||||
} else if (input instanceof RecipeDescriptor) {
|
||||
return ((RecipeDescriptor)input).displayName;
|
||||
}
|
||||
return "unknown";
|
||||
@@ -117,7 +136,7 @@ public class SelectRecipesDialog extends StatusDialog {
|
||||
public boolean isGrayed(Object element) {
|
||||
if (element instanceof RecipeDescriptor) {
|
||||
RecipeDescriptor r = (RecipeDescriptor) element;
|
||||
return model.getCheckedState(r) == CheckedState.GRAYED;
|
||||
return r.checked == CheckedState.GRAYED;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -126,7 +145,7 @@ public class SelectRecipesDialog extends StatusDialog {
|
||||
public boolean isChecked(Object element) {
|
||||
if (element instanceof RecipeDescriptor) {
|
||||
RecipeDescriptor r = (RecipeDescriptor) element;
|
||||
return model.getCheckedState(r) != CheckedState.UNCHECKED;
|
||||
return r.checked != CheckedState.UNCHECKED;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -168,7 +187,7 @@ public class SelectRecipesDialog extends StatusDialog {
|
||||
// Replace browser's built-in context menu with none
|
||||
docViewer.setMenu(new Menu(getShell(), SWT.NONE));
|
||||
|
||||
docViewer.setText(wrapHtml("Select a Recipe on the left to read description"));
|
||||
docViewer.setText(wrapHtml("Select a Recipe on the left to read description"));
|
||||
|
||||
|
||||
treeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
|
||||
@@ -200,7 +219,7 @@ public class SelectRecipesDialog extends StatusDialog {
|
||||
}
|
||||
|
||||
private void updateStatus() {
|
||||
boolean anythingSelected = Arrays.stream(model.getRecipeDescriptors()).anyMatch(d -> model.getCheckedState(d) != CheckedState.UNCHECKED);
|
||||
boolean anythingSelected = model.getRecipeDescriptors() != null && Arrays.stream(model.getRecipeDescriptors()).anyMatch(d -> d.checked != CheckedState.UNCHECKED);
|
||||
updateStatus(anythingSelected ? Status.info(SELECT_REWRITE_RECIPE_S_FROM_THE_LIST) : Status.error(SELECT_REWRITE_RECIPE_S_FROM_THE_LIST));
|
||||
}
|
||||
|
||||
@@ -211,12 +230,12 @@ public class SelectRecipesDialog extends StatusDialog {
|
||||
sb.append("</p>");
|
||||
sb.append("<ul>");
|
||||
for (OptionDescriptor option : r.options) {
|
||||
if (option.value != null) {
|
||||
if (option.value() != null) {
|
||||
sb.append("<li>");
|
||||
sb.append("<pre>");
|
||||
sb.append(option.value);
|
||||
sb.append(option.value());
|
||||
sb.append("</pre>");
|
||||
sb.append(option.description);
|
||||
sb.append(option.description());
|
||||
sb.append("</li>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,12 +52,18 @@ public class LoadUtils {
|
||||
}
|
||||
|
||||
public static Recipe createRecipe(RecipeDescriptor d, Function<String, Class<? extends Recipe>> getRecipeClass) {
|
||||
return createRecipe(d, getRecipeClass, false);
|
||||
}
|
||||
|
||||
public static Recipe createRecipe(RecipeDescriptor d, Function<String, Class<? extends Recipe>> getRecipeClass, boolean shallow) {
|
||||
Class<? extends Recipe> recipeClazz = getRecipeClass == null ? null : getRecipeClass.apply(d.getName());
|
||||
if (recipeClazz == null || DeclarativeRecipe.class.getName().equals(recipeClazz.getName())) {
|
||||
DeclarativeRecipe recipe = new DeclarativeRecipe(d.getName(), d.getDisplayName(), d.getDescription(),
|
||||
d.getTags(), d.getEstimatedEffortPerOccurrence(), d.getSource(), false, d.getMaintainers());
|
||||
for (RecipeDescriptor subDescriptor : d.getRecipeList()) {
|
||||
recipe.getRecipeList().add(createRecipe(subDescriptor, getRecipeClass));
|
||||
if (!shallow) {
|
||||
for (RecipeDescriptor subDescriptor : d.getRecipeList()) {
|
||||
recipe.getRecipeList().add(createRecipe(subDescriptor, getRecipeClass));
|
||||
}
|
||||
}
|
||||
return recipe;
|
||||
} else {
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -72,6 +73,7 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
public class RewriteRecipeRepository {
|
||||
@@ -99,6 +101,7 @@ 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 String CMD_REWRITE_SUBLIST = "sts/rewrite/sublist";
|
||||
private static final String CMD_REWRITE_RECIPE_EXECUTE = "sts/rewrite/recipe/execute";
|
||||
private static final Logger log = LoggerFactory.getLogger(RewriteRecipeRepository.class);
|
||||
|
||||
@@ -116,8 +119,9 @@ public class RewriteRecipeRepository {
|
||||
private Set<String> scanDirs;
|
||||
private Set<String> recipeFilters;
|
||||
|
||||
private static Gson serializationGson = new GsonBuilder()
|
||||
static final Gson serializationGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Duration.class, new DurationTypeConverter())
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
public RewriteRecipeRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder, BootJavaConfig config) {
|
||||
@@ -265,40 +269,117 @@ public class RewriteRecipeRepository {
|
||||
}
|
||||
|
||||
private static JsonElement recipeToJson(Recipe r) {
|
||||
JsonElement jsonElement = serializationGson.toJsonTree(r.getDescriptor());
|
||||
RecipeDescriptor descriptor = r.getDescriptor();
|
||||
JsonElement jsonElement = serializationGson.toJsonTree(Map.of(
|
||||
"name", descriptor.getName(),
|
||||
"displayName", descriptor.getDisplayName(),
|
||||
"description", descriptor.getDescription(),
|
||||
"options", descriptor.getOptions(),
|
||||
"tags", descriptor.getTags(),
|
||||
"hasSubRecipes", !descriptor.getRecipeList().isEmpty()
|
||||
));
|
||||
return jsonElement;
|
||||
}
|
||||
|
||||
CompletableFuture<List<Recipe>> getRootRecipes(Predicate<Recipe> rootFilter) {
|
||||
return recipes().thenApply(recipesMap -> recipesMap.values().stream().filter(rootFilter).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
CompletableFuture<List<Recipe>> getSubRecipes(String rootRecipeId, List<Integer> path) {
|
||||
return recipes().thenApply(recipesMap -> {
|
||||
Recipe recipe = recipesMap.get(rootRecipeId);
|
||||
for (int i : path) {
|
||||
if (i < recipe.getRecipeList().size()) {
|
||||
recipe = recipe.getRecipeList().get(i);
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
return recipe == null ? Collections.emptyList() : recipe.getRecipeList();
|
||||
});
|
||||
}
|
||||
|
||||
Recipe createRecipeFromSelection(Recipe original, RecipeSelectionDescriptor[] selection) {
|
||||
if (selection == null) {
|
||||
return original;
|
||||
} else {
|
||||
boolean sameSubrecipes = true;
|
||||
List<Recipe> newSubRecipes = new ArrayList<>(selection.length);
|
||||
for (int i = 0; i < selection.length; i++) {
|
||||
if (selection[i].selected) {
|
||||
Recipe originalSubRecipe = original.getRecipeList().get(i);
|
||||
Recipe newSubRecipe = createRecipeFromSelection(originalSubRecipe, selection[i].subselection());
|
||||
newSubRecipes.add(newSubRecipe);
|
||||
if (sameSubrecipes) {
|
||||
sameSubrecipes = newSubRecipe == originalSubRecipe;
|
||||
}
|
||||
} else {
|
||||
sameSubrecipes = false;
|
||||
}
|
||||
}
|
||||
if (sameSubrecipes) {
|
||||
return original;
|
||||
} else {
|
||||
@SuppressWarnings("unchecked")
|
||||
Recipe newRecipe = LoadUtils.createRecipe(original.getDescriptor(), id -> {
|
||||
try {
|
||||
return (Class<Recipe>) Class.forName(id);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}, true);
|
||||
newRecipe.getRecipeList().addAll(newSubRecipes);
|
||||
return newRecipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
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 listProjectRefactoringRecipes(uri.getAsString()).thenApply(recipes -> recipes.stream()
|
||||
.filter(RECIPE_LIST_FILTERS.get(f))
|
||||
.map(RewriteRecipeRepository::recipeToJson)
|
||||
.collect(Collectors.toList()));
|
||||
RecipeFilter f = params.getArguments().size() > 0 ? RecipeFilter.valueOf(((JsonElement) params.getArguments().get(0)).getAsString()) : RecipeFilter.ALL;
|
||||
|
||||
return getRootRecipes(r -> isAcceptableGlobalCommandRecipe(r) && RECIPE_LIST_FILTERS.get(f).test(r)).thenApply(recipes -> recipes.stream()
|
||||
.map(RewriteRecipeRepository::recipeToJson)
|
||||
.collect(Collectors.toList()));
|
||||
});
|
||||
|
||||
server.onCommand(CMD_REWRITE_SUBLIST, params -> {
|
||||
String rootRecipeId = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
JsonArray path = (JsonArray) params.getArguments().get(1);
|
||||
|
||||
return getSubRecipes(rootRecipeId, path.asList().stream().map(j -> j.getAsInt()).collect(Collectors.toList())).thenApply(recipes -> recipes.stream()
|
||||
.map(RewriteRecipeRepository::recipeToJson)
|
||||
.collect(Collectors.toList()));
|
||||
});
|
||||
|
||||
server.onCommand(CMD_REWRITE_EXECUTE, params -> {
|
||||
return recipes().thenCompose(recipes -> {
|
||||
return recipes().thenCompose(recipesMap -> {
|
||||
String uri = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
JsonElement recipesJson = ((JsonElement) params.getArguments().get(1));
|
||||
boolean needsConfirmation = params.getArguments().size() > 2 ? ((JsonElement) params.getArguments().get(2)).getAsBoolean() : false;
|
||||
|
||||
RecipeDescriptor d = serializationGson.fromJson(recipesJson, RecipeDescriptor.class);
|
||||
|
||||
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!");
|
||||
} else if (aggregateRecipe.getRecipeList().size() == 1) {
|
||||
Recipe r = aggregateRecipe.getRecipeList().get(0);
|
||||
RecipeSelectionDescriptor[] descriptors = serializationGson.fromJson(recipesJson, RecipeSelectionDescriptor[].class);
|
||||
List<Recipe> recipes = Arrays.stream(descriptors).map(d -> createRecipeFromSelection(recipesMap.get(d.id()), d.subselection())).collect(Collectors.toList());
|
||||
if (recipes.size() == 1) {
|
||||
Recipe r = recipes.get(0);
|
||||
String progressToken = params.getWorkDoneToken() == null
|
||||
|| params.getWorkDoneToken().getLeft() == null
|
||||
? (r.getName() == null ? UUID.randomUUID().toString() : r.getName())
|
||||
: params.getWorkDoneToken().getLeft();
|
||||
return apply(r, uri, progressToken, needsConfirmation);
|
||||
} else {
|
||||
String name = recipes.size() + " recipes";
|
||||
DeclarativeRecipe aggregateRecipe = new DeclarativeRecipe(
|
||||
name,
|
||||
name,
|
||||
recipes.stream().map(r -> r.getDescription()).collect(Collectors.joining("\n")),
|
||||
recipes.stream().flatMap(r -> r.getTags().stream()).collect(Collectors.toSet()),
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
Collections.emptyList()
|
||||
);
|
||||
aggregateRecipe.getRecipeList().addAll(recipes);
|
||||
String progressToken = params.getWorkDoneToken() == null
|
||||
|| params.getWorkDoneToken().getLeft() == null
|
||||
? (aggregateRecipe.getName() == null ? UUID.randomUUID().toString()
|
||||
@@ -397,21 +478,6 @@ public class RewriteRecipeRepository {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
* Therefore, it is best probably to list the available recipes and figure out if it is a Spring Boot project recipes is applied to
|
||||
* and if not throw an exception.
|
||||
*/
|
||||
// Optional<IJavaProject> projectOpt = projectFinder.find(new TextDocumentIdentifier(uri));
|
||||
// if (projectOpt.isPresent()) {
|
||||
return recipes().thenApply(recipes -> recipes.values().stream().filter(this::isAcceptableGlobalCommandRecipe).collect(Collectors.toList()));
|
||||
// }
|
||||
}
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
private static ProjectParser createRewriteProjectParser(IJavaProject jp, Function<Path, Parser.Input> inputProvider) {
|
||||
switch (jp.getProjectBuild().getType()) {
|
||||
case ProjectBuild.MAVEN_PROJECT_TYPE:
|
||||
@@ -438,47 +504,6 @@ public class RewriteRecipeRepository {
|
||||
return s;
|
||||
}
|
||||
|
||||
// private static Recipe convert(Recipe r, RecipeDescriptor d) {
|
||||
// try {
|
||||
// if (d.selected) {
|
||||
// if (d.children != null && !d.children.isEmpty()) {
|
||||
// Recipe recipe = r instanceof DeclarativeRecipe ? new DeclarativeRecipe(r.getName(), r.getDisplayName(), r.getDescription(), r.getTags(), r.getEstimatedEffortPerOccurrence(), null)
|
||||
// : r.getClass().getDeclaredConstructor().newInstance();
|
||||
// int i = 0;
|
||||
// for (Recipe sr : r.getRecipeList()) {
|
||||
// Recipe convertedSubRecipe = convert(sr, d.children.get(i++));
|
||||
// if (convertedSubRecipe != null) {
|
||||
// recipe.doNext(convertedSubRecipe);
|
||||
// }
|
||||
// }
|
||||
// return recipe;
|
||||
// } else {
|
||||
// return r;
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.error("", e);
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// @SuppressWarnings("unused")
|
||||
// private static class RecipeDescriptor {
|
||||
// String id;
|
||||
// String label;
|
||||
// String detail;
|
||||
// List<RecipeDescriptor> children;
|
||||
// boolean selected;
|
||||
//
|
||||
// RecipeDescriptor(Recipe r) {
|
||||
// this.id = r.getName();
|
||||
// this.label = r.getDisplayName();
|
||||
// this.detail = r.getDescription();
|
||||
// List<Recipe> subRecipes = r.getRecipeList();
|
||||
// if (r instanceof DeclarativeRecipe && !subRecipes.isEmpty() && (subRecipes.size() > 1 || subRecipes.get(0) instanceof DeclarativeRecipe)) {
|
||||
// this.children = r.getRecipeList().stream().map(sr -> new RecipeDescriptor(sr)).collect(Collectors.toList());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
record RecipeSelectionDescriptor(boolean selected, String id, RecipeSelectionDescriptor[] subselection) {};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*******************************************************************************
|
||||
* 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.rewrite;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository.RecipeSelectionDescriptor;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class RewriteRecipeRepositoryTest {
|
||||
|
||||
@Autowired RewriteRecipeRepository recipeRepo;
|
||||
|
||||
@Test
|
||||
void listSubRecipe() throws Exception {
|
||||
List<Recipe> recipes = recipeRepo.getSubRecipes("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1", List.of()).get();
|
||||
assertEquals(8, recipes.size());
|
||||
|
||||
recipes = recipeRepo.getSubRecipes("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1", List.of(0, 2)).get();
|
||||
assertEquals(13, recipes.size());
|
||||
|
||||
recipes = recipeRepo.getSubRecipes("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1",List.of(0, 345)).get();
|
||||
assertEquals(0, recipes.size());
|
||||
|
||||
recipes = recipeRepo.getSubRecipes("Hohoho", List.of()).get();
|
||||
assertEquals(0, recipes.size());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void listRootRecipes() throws Exception {
|
||||
List<Recipe> recipes = recipeRepo.getRootRecipes(r -> true).get();
|
||||
assertThat(recipes.size()).isGreaterThan(5);
|
||||
|
||||
recipes = recipeRepo.getRootRecipes(r -> r.getName().startsWith("Hohoho")).get();
|
||||
assertEquals(0, recipes.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createRecipeFromSelectionDescriptor() throws Exception {
|
||||
RecipeSelectionDescriptor descriptor = new RecipeSelectionDescriptor(true, "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1", new RecipeSelectionDescriptor[] {
|
||||
new RecipeSelectionDescriptor(true, "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", new RecipeSelectionDescriptor[] { // pick boot 3.0
|
||||
new RecipeSelectionDescriptor(true, "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7", null), // pick boot 2.7
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(true, "org.openrewrite.java.migrate.UpgradeToJava17", new RecipeSelectionDescriptor[] { // pick Java 17
|
||||
new RecipeSelectionDescriptor(true, "", null), // java 11
|
||||
new RecipeSelectionDescriptor(true, "", null), // java 17
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(true, "", null), // text blocks
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
}),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
}),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(true, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null),
|
||||
new RecipeSelectionDescriptor(false, "", null)
|
||||
});
|
||||
|
||||
Recipe boot31Recipes = recipeRepo.getRecipe("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1").get().get();
|
||||
|
||||
Recipe boot31 = recipeRepo.createRecipeFromSelection(boot31Recipes, descriptor.subselection());
|
||||
|
||||
assertEquals(2, boot31.getRecipeList().size());
|
||||
|
||||
Recipe security61 = boot31.getRecipeList().get(1);
|
||||
assertThat(security61.getRecipeList().size()).isGreaterThan(5);
|
||||
|
||||
Recipe boot30 = boot31.getRecipeList().get(0);
|
||||
assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", boot30.getName());
|
||||
assertEquals(2, boot30.getRecipeList().size());
|
||||
|
||||
Recipe boot27 = boot30.getRecipeList().get(0);
|
||||
assertThat(boot27.getRecipeList().size()).isGreaterThan(10);
|
||||
|
||||
Recipe java17 = boot30.getRecipeList().get(1);
|
||||
assertThat(java17.getName()).isEqualTo("org.openrewrite.java.migrate.UpgradeToJava17");
|
||||
assertThat(java17.getRecipeList().size()).isEqualTo(3);
|
||||
|
||||
assertThat(java17.getRecipeList().get(0).getName()).isEqualTo("org.openrewrite.java.migrate.Java8toJava11");
|
||||
assertThat(java17.getRecipeList().get(1).getName()).isEqualTo("org.openrewrite.java.migrate.JavaVersion17");
|
||||
assertThat(java17.getRecipeList().get(2).getName()).isEqualTo("org.openrewrite.java.migrate.lang.UseTextBlocks");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,11 +11,8 @@ interface RecipeDescriptor {
|
||||
displayName: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
estimatedEffortPerOccurrence: number;
|
||||
options: OptionDescriptor[];
|
||||
languages: string[];
|
||||
recipeList: RecipeDescriptor[];
|
||||
source: string;
|
||||
hasSubRecipes: boolean;
|
||||
}
|
||||
|
||||
interface OptionDescriptor {
|
||||
@@ -29,10 +26,16 @@ interface OptionDescriptor {
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface RecipeSelectionDescriptor {
|
||||
selected: boolean;
|
||||
id: string;
|
||||
subselection: RecipeSelectionDescriptor[];
|
||||
}
|
||||
|
||||
interface RecipeQuickPickItem extends VSCode.QuickPickItem{
|
||||
readonly id: string;
|
||||
selected: boolean;
|
||||
children: RecipeQuickPickItem[],
|
||||
children: RecipeQuickPickItem[] | undefined,
|
||||
readonly recipeDescriptor: RecipeDescriptor;
|
||||
}
|
||||
|
||||
@@ -95,45 +98,33 @@ async function showRefactorings(uri: VSCode.Uri, filter: string) {
|
||||
if (!uri) {
|
||||
uri = await getTargetPomXml();
|
||||
}
|
||||
const choices = await showCurrentPathQuickPick(VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true), filter).then((cmds: RecipeDescriptor[]) => cmds.map(convertToQuickPickItem)), []);
|
||||
const recipeDescriptors = choices.filter(i => i.selected).map(convertToRecipeDescriptor);
|
||||
const choices = await showCurrentPathQuickPick(VSCode.commands.executeCommand('sts/rewrite/list', filter).then((cmds: RecipeDescriptor[]) => cmds.map(d => convertToQuickPickItem(d, false))), []);
|
||||
const recipeDescriptors = choices.filter(i => i.selected).map(convertToRecipeSelectionDescriptor);
|
||||
const needsConfirmation = await shwoNeedsConfirmation();
|
||||
if (recipeDescriptors.length) {
|
||||
const aggregateRecipeDescriptor = recipeDescriptors.length === 1 ? recipeDescriptors[0] : {
|
||||
name: `${recipeDescriptors.length} recipes`,
|
||||
displayName: `${recipeDescriptors.length} recipes`,
|
||||
description: recipeDescriptors.map(d => d.description).join('\n'),
|
||||
tags: [...new Set<string>(recipeDescriptors.flatMap(d => d.tags))],
|
||||
languages: [...new Set<string>(recipeDescriptors.flatMap(d => d.languages))],
|
||||
options: [],
|
||||
recipeList: recipeDescriptors,
|
||||
estimatedEffortPerOccurrence: recipeDescriptors.filter(d => d.estimatedEffortPerOccurrence).map(d => d.estimatedEffortPerOccurrence).reduce((p, c) => p + c, 0)
|
||||
};
|
||||
if (aggregateRecipeDescriptor.estimatedEffortPerOccurrence === 0) {
|
||||
delete aggregateRecipeDescriptor.estimatedEffortPerOccurrence;
|
||||
}
|
||||
VSCode.commands.executeCommand('sts/rewrite/execute', uri.toString(true), aggregateRecipeDescriptor, needsConfirmation);
|
||||
VSCode.commands.executeCommand('sts/rewrite/execute', uri.toString(true), recipeDescriptors, needsConfirmation);
|
||||
} else {
|
||||
VSCode.window.showErrorMessage('No Recipes were selected!');
|
||||
}
|
||||
}
|
||||
|
||||
function convertToRecipeDescriptor(i: RecipeQuickPickItem): RecipeDescriptor {
|
||||
function convertToRecipeSelectionDescriptor(i: RecipeQuickPickItem): RecipeSelectionDescriptor {
|
||||
return {
|
||||
...i.recipeDescriptor,
|
||||
recipeList: i.children.filter(c => c.selected).map(convertToRecipeDescriptor)
|
||||
selected: i.selected,
|
||||
id: i.id,
|
||||
subselection: i.children ? i.children.map(convertToRecipeSelectionDescriptor) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
function convertToQuickPickItem(i: RecipeDescriptor): RecipeQuickPickItem {
|
||||
function convertToQuickPickItem(i: RecipeDescriptor, selected?: boolean): RecipeQuickPickItem {
|
||||
return {
|
||||
id: i.name,
|
||||
label: i.displayName,
|
||||
detail: i.options.filter(o => !!o.value).map(o => `${o.name}: ${JSON.stringify(o.value)}`).join('\n\n'),
|
||||
description: i.description,
|
||||
selected: false,
|
||||
children: i.recipeList ? i.recipeList.map(convertToQuickPickItem) : [],
|
||||
buttons: i.recipeList && i.recipeList.length ? [ SUB_RECIPES_BUTTON ] : undefined,
|
||||
selected: !!selected,
|
||||
children: undefined,
|
||||
buttons: i.hasSubRecipes ? [ SUB_RECIPES_BUTTON ] : undefined,
|
||||
recipeDescriptor: i
|
||||
};
|
||||
}
|
||||
@@ -188,7 +179,7 @@ function showCurrentPathQuickPick(itemsPromise: Thenable<RecipeQuickPickItem[]>,
|
||||
if (e.button === SUB_RECIPES_BUTTON) {
|
||||
currentItems.forEach(i => i.selected = quickPick.selectedItems.includes(i));
|
||||
itemsPath.push(e.item);
|
||||
showCurrentPathQuickPick(Promise.resolve(items), itemsPath).then(resolve, reject);
|
||||
showCurrentPathQuickPick(navigateToSubRecipes(e.item, itemsPath).then(() => items), itemsPath).then(resolve, reject);
|
||||
}
|
||||
});
|
||||
quickPick.onDidTriggerButton(b => {
|
||||
@@ -222,6 +213,17 @@ function showCurrentPathQuickPick(itemsPromise: Thenable<RecipeQuickPickItem[]>,
|
||||
});
|
||||
}
|
||||
|
||||
async function navigateToSubRecipes(item: RecipeQuickPickItem, itemsPath: RecipeQuickPickItem[]) {
|
||||
if (!item.children) {
|
||||
const indexPath = [];
|
||||
for (let i = 1; i < itemsPath.length; i++) {
|
||||
indexPath.push(itemsPath[i - 1].children.indexOf(itemsPath[i]));
|
||||
}
|
||||
const recipeDescriptors: RecipeDescriptor[] = await VSCode.commands.executeCommand('sts/rewrite/sublist', itemsPath[0].id, indexPath);
|
||||
item.children = recipeDescriptors.map(d => convertToQuickPickItem(d, item.selected));
|
||||
}
|
||||
}
|
||||
|
||||
function updateParentSelection(hierarchy: RecipeQuickPickItem[]): void {
|
||||
if (hierarchy.length) {
|
||||
const parent = hierarchy.pop();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"license": "EPL-1.0",
|
||||
"engines": {
|
||||
"npm": ">=6.0.0",
|
||||
"vscode": "^1.67.0"
|
||||
"vscode": "^1.75.0"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages",
|
||||
@@ -26,8 +26,6 @@
|
||||
"application-yaml"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onCommand:vscode-spring-boot.rewrite.list.refactorings",
|
||||
"onCommand:vscode-spring-boot.rewrite.list.boot-upgrades",
|
||||
"onCommand:vscode-spring-boot.ls.start"
|
||||
],
|
||||
"contributes": {
|
||||
@@ -1029,9 +1027,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.8.0",
|
||||
"@types/vscode": "1.67.0",
|
||||
"@types/vscode": "1.75.0",
|
||||
"typescript": "^4.8.0",
|
||||
"vsce": "^2.11.0"
|
||||
"vsce": "^2.15.0"
|
||||
},
|
||||
"extensionDependencies": [
|
||||
"redhat.java"
|
||||
|
||||
Reference in New Issue
Block a user