Eclipse UI for Rewrite Recipes

This commit is contained in:
aboyko
2022-06-21 15:35:54 -04:00
parent 382d0bbac6
commit 8bf0736a1d
6 changed files with 641 additions and 80 deletions

View File

@@ -175,8 +175,8 @@
</activeWhen>
</handler>
<handler
class="org.springframework.tooling.boot.ls.commands.UpgradeToBoot3Handler"
commandId="org.springframework.tooling.boot.ls.UpgradeToBoot3">
class="org.springframework.tooling.boot.ls.commands.RewriteRefactoringsHandler"
commandId="org.springframework.tooling.boot.ls.Rewrite">
</handler>
</extension>
<extension
@@ -194,9 +194,9 @@
</command>
<command
categoryId="org.springframework.ide.eclipse.commands"
description="Upgrade Spring Boot 2.x prohect to Spring Boot 3.x"
id="org.springframework.tooling.boot.ls.UpgradeToBoot3"
name="Upgrade to Boot 3">
description="Rewrite Refactorings for Spring Boot projects"
id="org.springframework.tooling.boot.ls.Rewrite"
name="Rewrite Refactorings...">
</command>
</extension>
@@ -372,9 +372,9 @@
allPopups="false"
locationURI="popup:org.springframework.ide.eclipse.ui.tools?after=boot">
<command
commandId="org.springframework.tooling.boot.ls.UpgradeToBoot3"
id="org.springframework.tooling.boot.ls.UpgradeToBoot3"
label="Upgrade to Boot 3"
commandId="org.springframework.tooling.boot.ls.Rewrite"
id="org.springframework.tooling.boot.ls.Rewrite"
label="Rewrite Refactorings..."
style="push">
<visibleWhen
checkEnabled="false">
@@ -389,7 +389,7 @@
<and>
<test
forcePluginActivation="true"
property="org.springsource.ide.eclipse.boot.isBoot2Resource">
property="org.springsource.ide.eclipse.boot.isBootResource">
</test>
<or>
<test
@@ -416,7 +416,7 @@
</test>
<test
forcePluginActivation="true"
property="org.springsource.ide.eclipse.boot.javaelement.isInBoot2Project">
property="org.springsource.ide.eclipse.boot.javaelement.isBootProject">
</test>
</and>
</or>

View File

@@ -0,0 +1,69 @@
/*******************************************************************************
* 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.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.Set;
class RecipeDescriptor {
String name;
String displayName;
String description;
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 {
String name;
String type;
String displayName;
String description;
String example;
List<String> valid;
boolean required;
Object value;
}
}

View File

@@ -0,0 +1,146 @@
/*******************************************************************************
* 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.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Status;
public class RecipeTreeModel {
public enum CheckedState {
UNCHECKED,
CHECKED,
GRAYED
}
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);
}
}
}
public void check(RecipeDescriptor d) {
if (simpleCheck(d)) {
inferCheckedStateFromChildren(parentMap.get(d));
}
}
private boolean simpleCheck(RecipeDescriptor d) {
if (checkedMap.get(d) != CheckedState.CHECKED) {
checkedMap.put(d, CheckedState.CHECKED);
for (RecipeDescriptor dc : d.recipeList) {
simpleCheck(dc);
}
return true;
}
return false;
}
public void uncheck(RecipeDescriptor d) {
if (simpleUncheck(d)) {
inferCheckedStateFromChildren(parentMap.get(d));
}
}
private boolean simpleUncheck(RecipeDescriptor d) {
if (checkedMap.get(d) != CheckedState.UNCHECKED) {
checkedMap.put(d, CheckedState.UNCHECKED);
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);
if (childState == CheckedState.UNCHECKED) {
all = false;
} else {
none = false;
}
}
CheckedState inferredState = CheckedState.GRAYED;
if (all) {
inferredState = CheckedState.CHECKED;
} else if (none) {
inferredState = CheckedState.UNCHECKED;
}
if (getCheckedState(d) != inferredState) {
checkedMap.put(d, inferredState);
inferCheckedStateFromChildren(parentMap.get(d));
}
}
}
public RecipeDescriptor[] getRecipeDescriptors() {
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.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;
}
}
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;
}
return null;
}
}

View File

@@ -0,0 +1,155 @@
/*******************************************************************************
* 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.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 java.util.concurrent.CompletableFuture;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.lsp4e.LanguageServiceAccessor;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.services.LanguageServer;
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.springsource.ide.eclipse.commons.livexp.util.Log;
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;
public class RewriteRefactoringsHandler extends AbstractHandler {
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())
.create();
private static final String REWRITE_REFACTORINGS_LIST = "sts/rewrite/list";
private static final String REWRITE_REFACTORINGS_EXEC = "sts/rewrite/execute";
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IStructuredSelection selection = HandlerUtil.getCurrentStructuredSelection(event);
Object o = selection.getFirstElement();
IProject project = null;
if (o instanceof IResource) {
project = ((IResource) o).getProject();
} else if (o instanceof IProject) {
project = (IProject) o;
} else if (o instanceof IAdaptable) {
project = ((IAdaptable) o).getAdapter(IProject.class);
}
if (project != null) {
List<@NonNull LanguageServer> usedLanguageServers = LanguageServiceAccessor
.getActiveLanguageServers(serverCapabilities -> true);
if (!usedLanguageServers.isEmpty()) {
final String uri = project.getLocationURI().toString();
ExecuteCommandParams commandParams = new ExecuteCommandParams();
commandParams.setCommand(REWRITE_REFACTORINGS_LIST);
commandParams.setArguments(List.of(uri));
try {
List<Object> allRewriteRecipesJson = new ArrayList<>();
List<Object> syncRecipesJson = Collections.synchronizedList(allRewriteRecipesJson);
CompletableFuture.allOf(usedLanguageServers.stream()
.map(ls -> ls.getWorkspaceService().executeCommand(commandParams).thenAccept(or -> {
if (or != null) {
syncRecipesJson.add(or);
}
}).exceptionally(t -> null))
.toArray(CompletableFuture[]::new)).thenRun(() -> {
allRewriteRecipesJson.stream().filter(List.class::isInstance).map(List.class::cast).findFirst().ifPresent(obj -> {
RecipeDescriptor[] descriptors = serializationGson.fromJson(serializationGson.toJson(obj), RecipeDescriptor[].class);
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
RecipeTreeModel recipesModel = new RecipeTreeModel(descriptors);
int returnCode = new SelectRecipesDialog(Display.getCurrent().getActiveShell(), recipesModel).open();
if (returnCode == Window.OK) {
try {
RecipeDescriptor recipeToApply = recipesModel.getSelectedRecipeDescriptors();
PlatformUI.getWorkbench().getProgressService().run(true, false, monitor -> {
try {
if (!usedLanguageServers.isEmpty()) {
monitor.beginTask("Applying recipe '" + recipeToApply.displayName + "'", IProgressMonitor.UNKNOWN);
ExecuteCommandParams cmdParams = new ExecuteCommandParams();
cmdParams.setCommand(REWRITE_REFACTORINGS_EXEC);
cmdParams.setArguments(List.of(
uri,
serializationGson.toJsonTree(recipeToApply)
));
CompletableFuture.allOf(usedLanguageServers.stream()
.map(ls -> ls.getWorkspaceService().executeCommand(cmdParams))
.toArray(CompletableFuture[]::new)).get();
}
} catch (Exception e) {
Log.log(e);
} finally {
monitor.done();
}
});
} catch (CoreException | InvocationTargetException | InterruptedException e) {
BootLanguageServerPlugin.getDefault().getLog().error(e.getMessage(), e);
}
}
});
});
});
} catch (Exception e) {
throw new ExecutionException("Failed to apply Rewrite recipe(s)", e);
}
}
}
return null;
}
}

View File

@@ -0,0 +1,261 @@
/*******************************************************************************
* 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.Arrays;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.StatusDialog;
import org.eclipse.jface.internal.text.html.HTMLPrinter;
import org.eclipse.jface.layout.FillLayoutFactory;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ICheckStateProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
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;
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 RecipeTreeModel model;
public SelectRecipesDialog(Shell parentShell, RecipeTreeModel model) {
super(parentShell);
setShellStyle(getShellStyle() | SWT.RESIZE);
this.model = model;
}
@Override
protected Control createDialogArea(Composite parent) {
SashForm form = new SashForm(parent, SWT.HORIZONTAL);
form.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
form.setLayout(new FillLayout());
Composite left = new Composite(form, SWT.NONE);
left.setLayout(FillLayoutFactory.fillDefaults().margins(MARGIN, MARGIN).create());
CheckboxTreeViewer treeViewer = new CheckboxTreeViewer(left);
treeViewer.setContentProvider(new ITreeContentProvider() {
@Override
public Object[] getElements(Object inputElement) {
if (inputElement instanceof RecipeTreeModel) {
return ((RecipeTreeModel) inputElement).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()]);
}
return new RecipeDescriptor[0];
}
@Override
public Object getParent(Object element) {
return null;
}
@Override
public boolean hasChildren(Object element) {
if (element instanceof RecipeDescriptor) {
RecipeDescriptor r = (RecipeDescriptor) element;
return r.recipeList != null && !r.recipeList.isEmpty();
}
return false;
}
});
treeViewer.setLabelProvider(LabelProvider.createTextProvider(input -> {
if (input instanceof RecipeDescriptor) {
return ((RecipeDescriptor)input).displayName;
}
return "unknown";
}));
treeViewer.setCheckStateProvider(new ICheckStateProvider() {
@Override
public boolean isGrayed(Object element) {
if (element instanceof RecipeDescriptor) {
RecipeDescriptor r = (RecipeDescriptor) element;
return model.getCheckedState(r) == CheckedState.GRAYED;
}
return false;
}
@Override
public boolean isChecked(Object element) {
if (element instanceof RecipeDescriptor) {
RecipeDescriptor r = (RecipeDescriptor) element;
return model.getCheckedState(r) != CheckedState.UNCHECKED;
}
return false;
}
});
treeViewer.addCheckStateListener(new ICheckStateListener() {
@Override
public void checkStateChanged(CheckStateChangedEvent event) {
if (event.getElement() instanceof RecipeDescriptor) {
RecipeDescriptor d = (RecipeDescriptor) event.getElement();
if (event.getChecked()) {
model.check(d);
} else {
model.uncheck(d);
}
treeViewer.refresh();
updateStatus();
}
}
});
treeViewer.setInput(model);
Composite right = new Composite(form, SWT.NONE);
right.setLayout(FillLayoutFactory.fillDefaults().extendedMargins(MARGIN, MARGIN).create());
Browser docViewer = new Browser(right, SWT.NONE);
docViewer.setJavascriptEnabled(false);
Display display= parent.getDisplay();
docViewer.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
docViewer.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
// Cancel opening of new windows
docViewer.addOpenWindowListener(event -> event.required= true);
// 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"));
treeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
Object o = event.getStructuredSelection().getFirstElement();
if (o instanceof RecipeDescriptor) {
docViewer.setText(wrapHtml(buildHtmlDescriptionSnippet((RecipeDescriptor) o)));
} else {
docViewer.setText(wrapHtml("Select a Recipe on the left to read description"));
}
}
});
form.setWeights(new int[] { 50, 50 });
return form;
}
@Override
protected Control createContents(Composite parent) {
Control c = super.createContents(parent);
setTitle("Select Rewrite Recipe");
parent.getDisplay().asyncExec(() -> updateStatus());
return c;
}
private void updateStatus() {
boolean anythingSelected = Arrays.stream(model.getRecipeDescriptors()).anyMatch(d -> model.getCheckedState(d) != CheckedState.UNCHECKED);
updateStatus(anythingSelected ? Status.info(SELECT_REWRITE_RECIPE_S_FROM_THE_LIST) : Status.error(SELECT_REWRITE_RECIPE_S_FROM_THE_LIST));
}
private String buildHtmlDescriptionSnippet(RecipeDescriptor r) {
StringBuilder sb = new StringBuilder();
sb.append("<p>");
sb.append(r.description);
sb.append("</p>");
sb.append("<ul>");
for (OptionDescriptor option : r.options) {
if (option.value != null) {
sb.append("<li>");
sb.append("<pre>");
sb.append(option.value);
sb.append("</pre>");
sb.append(option.description);
sb.append("</li>");
}
}
sb.append("</ul>");
return sb.toString();
}
private static String wrapHtml(String html) {
/*
* No JDT content. Means no JDT CSS part either. Therefore add JDT CSS chunk to it.
*/
ColorRegistry registry = JFaceResources.getColorRegistry();
RGB fgRGB = registry.getRGB("org.eclipse.jdt.ui.Javadoc.foregroundColor"); //$NON-NLS-1$
RGB bgRGB= registry.getRGB("org.eclipse.jdt.ui.Javadoc.backgroundColor"); //$NON-NLS-1$
StringBuilder buffer = new StringBuilder(html);
HTMLPrinter.insertPageProlog(buffer, 0, fgRGB, bgRGB, getStyleSheet());
HTMLPrinter.addPageEpilog(buffer);
return buffer.toString();
}
protected IDialogSettings getDialogBoundsSettings() {
String sectionName= getClass().getName() + "_dialogBounds"; //$NON-NLS-1$
IDialogSettings settings = PlatformUI
.getDialogSettingsProvider(FrameworkUtil.getBundle(getClass())).getDialogSettings();
IDialogSettings section= settings.getSection(sectionName);
if (section == null)
section= settings.addNewSection(sectionName);
return section;
}
/**
* Taken from {@link JavadocHover}. It's <code>private</code>. See {@link JavadocHover#getStyleSheet()}.
* @return CSS as string
*/
private static String getStyleSheet() {
if (fgStyleSheet == null) {
fgStyleSheet= JavadocHover.loadStyleSheet("/JavadocHoverStyleSheet.css"); //$NON-NLS-1$
}
String css= fgStyleSheet;
if (css != null) {
FontData fontData= JFaceResources.getFontRegistry().getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0];
css= HTMLPrinter.convertTopLevelFont(css, fontData);
}
return css;
}
}

View File

@@ -1,70 +0,0 @@
/*******************************************************************************
* 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.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.lsp4e.LanguageServiceAccessor;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.ui.handlers.HandlerUtil;
import org.springsource.ide.eclipse.commons.livexp.util.Log;
public class UpgradeToBoot3Handler extends AbstractHandler {
private static final String UPGRADE_TO_BOOT_3_COMMAND_ID = "sts/rewrite/recipe/org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0";
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IStructuredSelection selection = HandlerUtil.getCurrentStructuredSelection(event);
Object o = selection.getFirstElement();
IProject project = null;
if (o instanceof IResource) {
project = ((IResource) o).getProject();
} else if (o instanceof IProject) {
project = (IProject) o;
} else if (o instanceof IAdaptable) {
project = ((IAdaptable) o).getAdapter(IProject.class);
}
if (project != null) {
List<@NonNull LanguageServer> usedLanguageServers = LanguageServiceAccessor
.getActiveLanguageServers(serverCapabilities -> true);
if (!usedLanguageServers.isEmpty()) {
ExecuteCommandParams commandParams = new ExecuteCommandParams();
commandParams.setCommand(UPGRADE_TO_BOOT_3_COMMAND_ID);
commandParams.setArguments(List.of(project.getLocationURI().toString()));
try {
CompletableFuture.allOf(usedLanguageServers.stream()
.map(ls -> ls.getWorkspaceService().executeCommand(commandParams))
.toArray(CompletableFuture[]::new));
} catch (Exception e) {
Log.log(e);
throw new ExecutionException("Failed to perform Upgarde to Spring Boot 3", e);
}
}
}
return null;
}
}