From cd5d0d786acbbf49f2fccd4d657d53761360229a Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Wed, 15 Jun 2022 23:31:09 -0400 Subject: [PATCH] Show recipe parameters. Load recipes from descriptors. --- .../ide/vscode/commons/rewrite/LoadUtils.java | 164 ++++++++++++++++++ .../META-INF/rewrite/spring-boot3-upgrade.yml | 14 +- .../vscode/commons/rewrite/LoadUtilsTest.java | 63 +++++++ .../rewrite/RecipesDescriptionGenerator.java | 67 +++++++ .../java/rewrite/RewriteRecipeRepository.java | 144 +++++++-------- .../vscode-spring-boot/lib/rewrite.ts | 79 ++++++--- 6 files changed, 434 insertions(+), 97 deletions(-) create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipesDescriptionGenerator.java diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java new file mode 100644 index 000000000..499fc5a67 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * 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; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.openrewrite.Recipe; +import org.openrewrite.config.DeclarativeRecipe; +import org.openrewrite.config.OptionDescriptor; +import org.openrewrite.config.RecipeDescriptor; +import org.openrewrite.config.RecipeIntrospectionException; +import org.openrewrite.internal.RecipeIntrospectionUtils; + +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 LoadUtils { + + public static class DurationTypeConverter implements JsonSerializer, JsonDeserializer { + @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()); + } + } + + @SuppressWarnings("unchecked") + public static Recipe createRecipe(RecipeDescriptor d) { + try { + Class recipeClazz = (Class) Class.forName(d.getName()); + Recipe recipe = constructRecipe(recipeClazz, d.getOptions()); + return recipe; + } catch (ClassNotFoundException e) { + DeclarativeRecipe recipe = new DeclarativeRecipe(d.getName(), d.getDisplayName(), d.getDescription(), d.getTags(), d.getEstimatedEffortPerOccurrence(), d.getSource()); + for (RecipeDescriptor subDescriptor : d.getRecipeList()) { + recipe.doNext(createRecipe(subDescriptor)); + } + return recipe; + } + } + + public static Recipe constructRecipe(Class recipeClass, List options) { + Constructor primaryConstructor = RecipeIntrospectionUtils.getZeroArgsConstructor(recipeClass); + if (primaryConstructor == null) { + primaryConstructor = RecipeIntrospectionUtils.getPrimaryConstructor(recipeClass); + } + Object[] constructorArgs = new Object[primaryConstructor.getParameterCount()]; + List remainingOptions = new ArrayList<>(options); + for (int i = 0; i < primaryConstructor.getParameters().length; i++) { + java.lang.reflect.Parameter param = primaryConstructor.getParameters()[i]; + int j = 0; + for (; j < remainingOptions.size() && !param.getName().equals(remainingOptions.get(j).getName()); j++) { + // nothing + } + if (j < remainingOptions.size()) { + // found in option descriptors + OptionDescriptor optionDescriptor = remainingOptions.remove(j); + constructorArgs[i] = getOptionValueForType(param.getType(), optionDescriptor); + } else { + // param not found in option descriptors + if (param.getType().isPrimitive()) { + constructorArgs[i] = getPrimitiveDefault(param.getType()); + } else { + constructorArgs[i] = null; + } + } + } + primaryConstructor.setAccessible(true); + try { + Recipe recipe = (Recipe) primaryConstructor.newInstance(constructorArgs); + for (OptionDescriptor option : remainingOptions) { + if (option.getValue() != null) { + Field f = recipe.getClass().getDeclaredField(option.getName()); + f.setAccessible(true); + f.set(recipe, getOptionValueForType(f.getType(), option)); + } + } + return recipe; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchFieldException | SecurityException e) { + // Should never happen + throw new RecipeIntrospectionException("Unable to call primary constructor for Recipe " + recipeClass, e); + } + } + + private static Object getOptionValueForType(Class type, OptionDescriptor option) { + if (option.getValue() instanceof Collection && type.isArray()) { + Collection arrayValue = (Collection) option.getValue(); + Object[] valueToSet = (Object[]) Array.newInstance(type.getComponentType(), arrayValue.size()); + int k = 0; + for (Object v : arrayValue) { + valueToSet[k] = getOptionValue(type.getComponentType().getSimpleName(), v); + } + return valueToSet; + } else { + return getOptionValue(option.getType(), option.getValue()); + } + } + + private static Object getOptionValue(String type, Object v) { + switch (type) { + case "int": + return ((Number) v).intValue(); + case "long": + return ((Number) v).longValue(); + case "short": + return ((Number) v).shortValue(); + case "float": + return ((Number) v).floatValue(); + case "double": + return ((Number) v).doubleValue(); + default: + return v; + } + } + + private static Object getPrimitiveDefault(Class t) { + if (t.equals(byte.class)) { + return (byte) 0; + } else if (t.equals(short.class)) { + return (short) 0; + } else if (t.equals(int.class)) { + return 0; + } else if (t.equals(long.class)) { + return 0L; + } else if (t.equals(float.class)) { + return 0.0f; + } else if (t.equals(double.class)) { + return 0.0d; + } else if (t.equals(char.class)) { + return '\u0000'; + } else if (t.equals(boolean.class)) { + return false; + } else { + throw new RecipeIntrospectionException(t.getCanonicalName() + " is not a supported primitive type"); + } + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot3-upgrade.yml b/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot3-upgrade.yml index 38c1735c6..6bfb32258 100644 --- a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot3-upgrade.yml +++ b/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot3-upgrade.yml @@ -9,7 +9,7 @@ recipeList: # Upgrade 3.0.x from 2.x - org.openrewrite.java.spring.boot3.MavenPomUpgrade - org.openrewrite.java.spring.boot3.data.UpgradeSpringData_3_0 -# - org.openrewrite.java.spring.boot3.SpringBootProperties_3_0 + - org.openrewrite.java.spring.boot3.Micrometer_3_0 --- ######################################################################################################################## # SpringBoot 3_0 Maven Pom @@ -31,5 +31,17 @@ recipeList: key: 'java.version' newValue: 17 addIfMissing: true +--- +######################################################################################################################## +# Spring Data 3.0 io.micrometer.core.instrument.binder -> io.micrometer.binder +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.spring.boot3.Micrometer_3_0 +displayName: Micrometer compatible with Boot 3.x +description: Switch to Micrometer compatible with Boot 3.x +recipeList: + - org.openrewrite.java.ChangePackage: + oldPackageName: io.micrometer.core.instrument.binder + newPackageName: io.micrometer.binder + recursive: true \ No newline at end of file diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java new file mode 100644 index 000000000..be8201307 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.openrewrite.Recipe; +import org.openrewrite.config.DeclarativeRecipe; +import org.openrewrite.config.Environment; +import org.openrewrite.config.RecipeDescriptor; +import org.openrewrite.maven.UpgradeDependencyVersion; + +public class LoadUtilsTest { + + private static Environment env; + + @BeforeClass + public static void setupAll() { + env = Environment.builder().scanRuntimeClasspath().build(); + } + + @Test + public void createRecipeTest() throws Exception { + RecipeDescriptor recipeDescriptor = env.listRecipeDescriptors().stream().filter(d -> "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0".equals(d.getName())).findFirst().orElse(null); + assertNotNull(recipeDescriptor); + Recipe r = LoadUtils.createRecipe(recipeDescriptor); + + assertTrue(r instanceof DeclarativeRecipe); + assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", r.getName()); + assertEquals("Upgrade to Spring Boot 3.0 from prior 2.x version.", r.getDescription()); + assertEquals("Upgrade to Spring Boot 3.0 from 2.x", r.getDisplayName()); + assertEquals(3, r.getRecipeList().size()); + + Recipe pomRecipe = r.getRecipeList().get(0); + assertTrue(pomRecipe instanceof DeclarativeRecipe); + assertEquals("org.openrewrite.java.spring.boot3.MavenPomUpgrade", pomRecipe.getName()); + assertEquals("Upgrade Maven Pom to Spring Boot 3.0 from prior 2.x version.", pomRecipe.getDescription()); + assertEquals("Upgrade Maven Pom to Spring Boot 3.0 from 2.x", pomRecipe.getDisplayName()); + assertEquals(3, pomRecipe.getRecipeList().size()); + + r = pomRecipe.getRecipeList().get(0); + assertTrue(r instanceof UpgradeDependencyVersion); + UpgradeDependencyVersion upgradeDependencyRecipe = (UpgradeDependencyVersion) r; + assertEquals("org.openrewrite.maven.UpgradeDependencyVersion", upgradeDependencyRecipe.getName()); + assertEquals("Upgrade Maven dependency version", upgradeDependencyRecipe.getDisplayName()); + assertEquals(0, upgradeDependencyRecipe.getRecipeList().size()); + assertTrue(upgradeDependencyRecipe.getNewVersion().startsWith("3.0.")); + assertEquals("org.springframework.boot", upgradeDependencyRecipe.getGroupId()); + assertEquals("*", upgradeDependencyRecipe.getArtifactId()); + } +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipesDescriptionGenerator.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipesDescriptionGenerator.java new file mode 100644 index 000000000..97de96955 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipesDescriptionGenerator.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * 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.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.stream.Collectors; + +import org.openrewrite.config.Environment; +import org.openrewrite.config.OptionDescriptor; +import org.openrewrite.config.RecipeDescriptor; + +public class RecipesDescriptionGenerator { + + public static void main(String[] args) throws IOException { + String s = Environment.builder().scanRuntimeClasspath().build().listRecipeDescriptors().stream() + .filter(d -> RewriteRecipeRepository.TOP_LEVEL_RECIPES.contains(d.getName())) + .map(d -> convertToMarkdown(d, 1)) + .collect(Collectors.joining("\n\n")); + + Path path = Paths.get("recipes.md"); + path = path.toFile().getCanonicalFile().toPath(); + System.out.println("Saving to file: " + path); + Files.write(path, s.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + + } + + private static String convertToMarkdown(RecipeDescriptor d, int level) { + StringBuilder sb = new StringBuilder(); + if (!"org.openrewrite.yaml.ChangePropertyKey".equals(d.getName())) { + for (int i = 0; i < level; i++) { + sb.append('#'); + } + sb.append(' '); + sb.append(d.getDisplayName()); + sb.append(" ("); + sb.append(d.getName()); + sb.append(")\n"); + sb.append(d.getDescription()); + sb.append("\n\n"); + for (OptionDescriptor option : d.getOptions()) { + sb.append("- **"); + sb.append(option.getDisplayName()); + sb.append("**: "); + sb.append(": `"); + sb.append(option.getValue()); + sb.append("`\n"); + } + sb.append("\n"); + sb.append(d.getRecipeList().stream().map(cd -> convertToMarkdown(cd, level + 1)).collect(Collectors.joining("\n\n"))); + } + return sb.toString(); + } + +} 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 12a7f90b0..b39eba0d4 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 @@ -13,8 +13,8 @@ package org.springframework.ide.vscode.boot.java.rewrite; import java.io.File; 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; @@ -39,8 +39,8 @@ import org.openrewrite.Recipe; import org.openrewrite.Result; import org.openrewrite.SourceFile; import org.openrewrite.Validated; -import org.openrewrite.config.DeclarativeRecipe; import org.openrewrite.config.Environment; +import org.openrewrite.config.RecipeDescriptor; import org.openrewrite.java.JavaParser; import org.openrewrite.maven.MavenParser; import org.slf4j.Logger; @@ -50,6 +50,8 @@ 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.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.maven.MavenProjectParser; @@ -57,6 +59,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; public class RewriteRecipeRepository { @@ -75,7 +78,7 @@ public class RewriteRecipeRepository { final public CompletableFuture loaded; - private static final Set TOP_LEVEL_RECIPES = Set.of( + static final Set TOP_LEVEL_RECIPES = Set.of( "org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration", "org.openrewrite.java.spring.boot2.SpringBoot2BestPractices", "org.openrewrite.java.spring.boot2.SpringBoot1To2Migration", @@ -85,6 +88,10 @@ public class RewriteRecipeRepository { "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0" ); + private static Gson serializationGson = new GsonBuilder() + .registerTypeAdapter(Duration.class, new DurationTypeConverter()) + .create(); + public RewriteRecipeRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder) { this.server = server; this.projectFinder = projectFinder; @@ -95,6 +102,7 @@ public class RewriteRecipeRepository { private void loadRecipes() { try { + server.getProgressService().progressEvent(RECIPES_LOADING_PROGRESS, "Loading Rewrite Recipes..."); log.info("Loading Rewrite Recipes..."); for (Recipe r : Environment.builder().scanRuntimeClasspath().build().listRecipes()) { if (r.getName() != null) { @@ -119,6 +127,7 @@ public class RewriteRecipeRepository { log.info("Done loading Rewrite Recipes"); server.doOnInitialized(() -> registerCommands()); } catch (Throwable t) { + server.getProgressService().progressEvent(RECIPES_LOADING_PROGRESS, null); log.error("", t); } } @@ -127,46 +136,39 @@ public class RewriteRecipeRepository { return Optional.ofNullable(recipes.get(name)); } + private static JsonElement recipeToJson(Recipe r) { + JsonElement jsonElement = serializationGson.toJsonTree(r.getDescriptor()); + return jsonElement; + } + private void registerCommands() { log.info("Registering commands for rewrite recipes..."); Builder listBuilder = ImmutableList.builder(); - + server.onCommand("sts/rewrite/list", params -> { JsonElement uri = (JsonElement) params.getArguments().get(0); - return CompletableFuture.completedFuture(uri == null ? Collections.emptyList() : listProjectRefactoringCommands(uri.getAsString())); + return CompletableFuture.completedFuture(uri == null ? Collections.emptyList() : listProjectRefactoringRecipes(uri.getAsString()).stream().map(RewriteRecipeRepository::recipeToJson).collect(Collectors.toList())); }); listBuilder.add("sts/rewrite/list"); server.onCommand("sts/rewrite/execute", params -> { String uri = ((JsonElement) params.getArguments().get(0)).getAsString(); JsonElement recipesJson = ((JsonElement) params.getArguments().get(1)); - RecipeDescriptor[] recipeDescriptors = new Gson().fromJson(recipesJson, RecipeDescriptor[].class); - List convertedRecipes = Arrays.stream(recipeDescriptors) - .filter(rd -> rd.selected) - .filter(rd -> recipes.containsKey(rd.id)) - .map(rd -> convert(recipes.get(rd.id), rd)) - .collect(Collectors.toList()); - if (convertedRecipes.isEmpty()) { + + RecipeDescriptor d = serializationGson.fromJson(recipesJson, RecipeDescriptor.class); + + Recipe aggregateRecipe = LoadUtils.createRecipe(d); + + if (aggregateRecipe.getRecipeList().isEmpty()) { throw new RuntimeException("Not recipes to execute!"); - } else if (convertedRecipes.size() == 1) { - Recipe r = convertedRecipes.get(0); + } else if (aggregateRecipe.getRecipeList().size() == 1) { + Recipe r = aggregateRecipe.getRecipeList().get(0); String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? r.getName() : params.getWorkDoneToken().getLeft(); return apply(r, uri, progressToken); } else { - Recipe multiRecipe = new Recipe() { - - @Override - public String getDisplayName() { - return convertedRecipes.size() + " recipes"; - } - - }; - for (Recipe r : convertedRecipes) { - multiRecipe.doNext(r); - } - String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? multiRecipe.getName() : params.getWorkDoneToken().getLeft(); - return apply(multiRecipe, uri, progressToken); + String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? aggregateRecipe.getName() : params.getWorkDoneToken().getLeft(); + return apply(aggregateRecipe, uri, progressToken); } }); listBuilder.add("sts/rewrite/execute"); @@ -248,13 +250,13 @@ public class RewriteRecipeRepository { return ORDocUtils.createWorkspaceEdit(absoluteProjectDir, server.getTextDocumentService(), results); } - private List listProjectRefactoringCommands(String uri) { + private List listProjectRefactoringRecipes(String uri) { if (uri != null) { Optional projectOpt = projectFinder.find(new TextDocumentIdentifier(uri)); if (projectOpt.isPresent()) { - List commandDescriptors = new ArrayList<>(globalCommandRecipes.size()); + List commandDescriptors = new ArrayList<>(globalCommandRecipes.size()); for (Recipe r : globalCommandRecipes) { - commandDescriptors.add(new RecipeDescriptor(r)); + commandDescriptors.add(r); } return commandDescriptors; } @@ -287,47 +289,47 @@ public class RewriteRecipeRepository { } } - 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; - } +// 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 children; - boolean selected; - - RecipeDescriptor(Recipe r) { - this.id = r.getName(); - this.label = r.getDisplayName(); - this.detail = r.getDescription(); - List 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()); - } - } - } +// @SuppressWarnings("unused") +// private static class RecipeDescriptor { +// String id; +// String label; +// String detail; +// List children; +// boolean selected; +// +// RecipeDescriptor(Recipe r) { +// this.id = r.getName(); +// this.label = r.getDisplayName(); +// this.detail = r.getDescription(); +// List 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()); +// } +// } +// } } diff --git a/vscode-extensions/vscode-spring-boot/lib/rewrite.ts b/vscode-extensions/vscode-spring-boot/lib/rewrite.ts index d2817273a..87279e8c7 100644 --- a/vscode-extensions/vscode-spring-boot/lib/rewrite.ts +++ b/vscode-extensions/vscode-spring-boot/lib/rewrite.ts @@ -3,18 +3,34 @@ import { LanguageClient } from "vscode-languageclient/node"; import * as VSCode from 'vscode'; import * as path from "path"; -interface RewriteCommandInfo { - id: string; - label: string; - detail?: string; - children: RewriteCommandInfo[]; - selected: boolean; +interface RecipeDescriptor { + name: string; + displayName: string; + description: string; + tags: string[]; + estimatedEffortPerOccurrence: number; + options: OptionDescriptor[]; + languages: string[]; + recipeList: RecipeDescriptor[]; + source: string; +} + +interface OptionDescriptor { + name: string; + type: string; + displayName: string; + description: string; + example: string; + valid: string[] | undefined; + required: boolean; + value: any; } interface RecipeQuickPickItem extends VSCode.QuickPickItem{ - id: string; - children: RecipeQuickPickItem[]; + readonly id: string; selected: boolean; + children: RecipeQuickPickItem[], + readonly recipeDescriptor: RecipeDescriptor; } function getWorkspaceFolderName(file: VSCode.Uri): string { @@ -73,34 +89,47 @@ async function liveHoverConnectHandler(uri: VSCode.Uri) { if (!uri) { uri = await getTargetPomXml(); } - const cmds: RewriteCommandInfo[] = await VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true)); + const cmds: RecipeDescriptor[] = await VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true)); const choices = cmds.map(convertToQuickPickItem); await showCurrentPathQuickPick(choices, []); - const recipesInfo = choices.filter(i => i.selected).map(convertToRecipeDescriptor); - if (recipesInfo.length) { - VSCode.commands.executeCommand('sts/rewrite/execute', uri.toString(true), recipesInfo); + const recipeDescriptors = choices.filter(i => i.selected).map(convertToRecipeDescriptor); + 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(recipeDescriptors.flatMap(d => d.tags))], + languages: [...new Set(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); } else { VSCode.window.showErrorMessage('No Recipes were selected!'); } } -function convertToRecipeDescriptor(i: RecipeQuickPickItem): RewriteCommandInfo { +function convertToRecipeDescriptor(i: RecipeQuickPickItem): RecipeDescriptor { return { - id: i.id, - label: i.label, - selected: i.selected, - children: i.children.map(convertToRecipeDescriptor) + ...i.recipeDescriptor, + recipeList: i.children.filter(c => c.selected).map(convertToRecipeDescriptor) }; } -function convertToQuickPickItem(i: RewriteCommandInfo): RecipeQuickPickItem { +function convertToQuickPickItem(i: RecipeDescriptor): RecipeQuickPickItem { return { - id: i.id, - label: i.label, - detail: i.detail, - selected: i.selected, - children: i.children ? i.children.map(convertToQuickPickItem) : [], - buttons: i.children && i.children.length ? [ SUB_RECIPES_BUTTON ] : undefined, + 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, + recipeDescriptor: i }; } @@ -112,7 +141,7 @@ function showCurrentPathQuickPick(items: RecipeQuickPickItem[], itemsPath: Recip parent = currentItems.find(i => i === p); currentItems = parent.children; }); - const quickPick = VSCode.window.createQuickPick(); + const quickPick = VSCode.window.createQuickPick(); quickPick.items = currentItems; quickPick.title = 'Select Recipes'; quickPick.canSelectMany = true;