diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index aba2ec97f4..e985308028 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -108,6 +108,7 @@ public CommandletManagerImpl(IdeContext context) { add(new Node(context)); add(new Npm(context)); add(new Mvn(context)); + add(new RefactorCommandlet(context)); add(new GcViewer(context)); add(new Gradle(context)); add(new Eclipse(context)); diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RefactorCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RefactorCommandlet.java new file mode 100644 index 0000000000..77e1d14355 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RefactorCommandlet.java @@ -0,0 +1,109 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.property.EnumProperty; +import com.devonfw.tools.ide.property.StringProperty; +import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.tool.mvn.Mvn; +import com.devonfw.tools.ide.tool.openrewrite.RecipeManager; +import com.devonfw.tools.ide.tool.openrewrite.RecipeWrapper; +import com.devonfw.tools.ide.tool.openrewrite.RefactorRecipeEnum; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Scanner; + +/** + * {@link ToolCommandlet} for Refactor. + */ +public class RefactorCommandlet extends Commandlet { + + public final EnumProperty command; + public final StringProperty arguments; + private RecipeManager recipeManager; + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public RefactorCommandlet(IdeContext context) { + + super(context); + addKeyword(getName()); + this.command = add(new EnumProperty<>("", true, "recipe_name", RefactorRecipeEnum.class)); + this.arguments = new StringProperty("", false, true, "recipe-extra-arguments"); + recipeManager = new RecipeManager(); + add(this.arguments); + //this.recipe = context. + } + + @Override + public String getName() { + //this indicates the command name + return "refactor"; + } + + private String[] adaptMVNCommand(String recipeRawCommands) { + if(recipeRawCommands.startsWith("mvn")) { + return recipeRawCommands.replaceFirst("\\Qmvn\\E", "").split("\\s+"); + } else { + return recipeRawCommands.split("\\s+"); + } + } + + private String changeToDryRunCommand(String recipeRawCommands) { + return recipeRawCommands.replaceAll(":run\\b", ":dryrun"); + } + + private void showInfo(RecipeWrapper wrapper) { + context.info("Recipe [{}], {} ", wrapper.ideasy_command.name(), wrapper.description); + context.info("Reference {}", wrapper.url); + context.info("Raw command: {}", wrapper.raw_cmd); + } + + private boolean confirmApplyChange() { + context.info("***Before making actual changes to the code, please confirm it seriously. It is strongly recommended to perform a DRY-RUN first***"); + context.info("Type yes to apply changes, or press other keys to perform DRY-RUN: "); + + Scanner scanner = new Scanner(System.in); + String input = scanner.nextLine(); + + return (input.equalsIgnoreCase("yes")); + } + + @Override + public void run() { + + context.info("{} called", getClass().getSimpleName()); + + RefactorRecipeEnum command = this.command.getValue(); + String option = this.arguments.getValue(); + + if(!recipeManager.isValidRecipeEnum(command)) { + context.error("INVALID recipe name: {}", command); + return; + } + + RecipeWrapper wrapper = recipeManager.getRecipeWrapper(command); + + showInfo(wrapper); + + String commandLine = wrapper.raw_cmd; + + if(!confirmApplyChange()) { + commandLine = changeToDryRunCommand(commandLine); + } + + context.info("Actual command line: {}", commandLine); + + getCommandlet(Mvn.class).runTool(adaptMVNCommand(commandLine)); + + } + + @Override + public boolean isIdeHomeRequired() { + + return false; + } +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/RefactorRecipeProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/RefactorRecipeProperty.java new file mode 100644 index 0000000000..04a1321aa8 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RefactorRecipeProperty.java @@ -0,0 +1,50 @@ +package com.devonfw.tools.ide.property; + +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.completion.CompletionCandidateCollector; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.tool.plugin.PluginBasedCommandlet; +import com.devonfw.tools.ide.tool.plugin.ToolPluginDescriptor; +import com.devonfw.tools.ide.tool.plugin.ToolPlugins; +import com.devonfw.tools.ide.validation.PropertyValidator; + +public class RefactorRecipeProperty extends Property { + + public RefactorRecipeProperty(String name) { + + this(name, null); + } + + public RefactorRecipeProperty(String name, PropertyValidator validator) { + + super(name, true, null, true, validator); + } + + @Override + public Class getValueType() { + + return String.class; + } + + @Override + public String parse(String valueAsString, IdeContext context) { + + return valueAsString; + } + + @Override + protected void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { + + ToolCommandlet cmd = commandlet.getToolForCompletion(); + if (cmd instanceof PluginBasedCommandlet pbc) { + ToolPlugins plugins = pbc.getPlugins(); + for (ToolPluginDescriptor pluginDescriptor : plugins.getPlugins()) { + if (pluginDescriptor.name().toLowerCase().startsWith(arg.toLowerCase())) { + collector.add(pluginDescriptor.name(), null, null, commandlet); + } + } + } + } + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/openrewrite/RecipeManager.java b/cli/src/main/java/com/devonfw/tools/ide/tool/openrewrite/RecipeManager.java new file mode 100644 index 0000000000..530606dcde --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/openrewrite/RecipeManager.java @@ -0,0 +1,62 @@ +package com.devonfw.tools.ide.tool.openrewrite; + +import com.devonfw.tools.ide.json.JsonMapping; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + + +public class RecipeManager { + + private static final String OPEN_REWRITE_CONFIG_JSON_PATH = "refactor/openrewrite.json"; + private final Map recipes = new HashMap<>(); + + + public RecipeManager() { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader( + Objects.requireNonNull(RecipeManager.class.getClassLoader().getResourceAsStream(OPEN_REWRITE_CONFIG_JSON_PATH)), StandardCharsets.UTF_8)); + ObjectMapper objectMapper = JsonMapping.create(); + + List wrapperList = objectMapper.readValue(reader, objectMapper.getTypeFactory().constructCollectionType(List.class, RecipeWrapper.class)); + + for(RecipeWrapper one: wrapperList) { + recipes.put(one.ideasy_command, one); + } + + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public List listAvailableRecipes() { + return Collections.unmodifiableList(recipes.values().stream().toList()); + } + + private Optional findRecipeByName(String rawName) { + return recipes.values().stream().filter(x -> x.origin_name.equals(rawName)).findAny(); + } + + public boolean isValidRecipeNameRawName(String rawName) { + return findRecipeByName(rawName).isPresent(); + } + + public boolean isValidRecipeEnum(RefactorRecipeEnum recipeEnum) { + return recipes.containsKey(recipeEnum); + } + + public RecipeWrapper getRecipeWrapper(RefactorRecipeEnum recipeEnum) { + return recipes.get(recipeEnum); + } + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/openrewrite/RecipeWrapper.java b/cli/src/main/java/com/devonfw/tools/ide/tool/openrewrite/RecipeWrapper.java new file mode 100644 index 0000000000..c13de47c0b --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/openrewrite/RecipeWrapper.java @@ -0,0 +1,17 @@ +package com.devonfw.tools.ide.tool.openrewrite; + +import java.util.Locale; + +public class RecipeWrapper { + public String description; + public String origin_name; + public String url; + public RefactorRecipeEnum ideasy_command; + public String raw_cmd; + + //in case of future need + public String getName() { + return this.origin_name; + } + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/openrewrite/RefactorRecipeEnum.java b/cli/src/main/java/com/devonfw/tools/ide/tool/openrewrite/RefactorRecipeEnum.java new file mode 100644 index 0000000000..c72c299526 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/openrewrite/RefactorRecipeEnum.java @@ -0,0 +1,5 @@ +package com.devonfw.tools.ide.tool.openrewrite; + +public enum RefactorRecipeEnum { + FORMAT_JAVA_CODE, REMOVE_BLANK_LINES, UNRECOGNIZED_RECIPE +} diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index be1c8cc704..22c97039ee 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -88,6 +88,9 @@ cmd.python=Tool commandlet for Python. cmd.python.detail=Python is an object-oriented programming language, comparable to Perl, Ruby, Scheme, or Java. Detailed documentation can be found at https://www.python.org/doc/ cmd.quarkus=Tool commandlet for Quarkus (framework for cloud-native apps). cmd.quarkus.detail=Quarkus is a Kubernetes-native Java framework for building cloud-native applications. Detailed documentation can be found at https://quarkus.io/ +cmd.refactor=Refactor existing code base with specific recipes provided by OpenRewrite +cmd.refactor.detail=OpenRewrite is a popular tool for refactoring (meta-programming). Detailed documentation can be found at https://docs.openrewrite.org/ +cmd.refactor.val.recipe_name=Refactor recipe names (RECIPE_1|RECIPE_2|RECIPE_3) cmd.repository=Set up pre-configured git repositories using 'ide repository setup ' cmd.repository.detail=Without further arguments this will set up all pre-configured git repositories.\nAlso, you can provide an explicit git repo as `` argument and IDEasy will automatically clone, build and set up your project based on the existing property file.\nRepositories are configured in 'settings/repository/.properties' and can therefore be shared with your project team for automatic or optional setup. cmd.repository.val.repository=The name of the properties file of the pre-configured git repository to set up, omit to set up all active repositories. @@ -155,6 +158,7 @@ val.cfg=Selection of the configuration file (settings | home | conf | workspace) val.commandlet=The selected commandlet (use 'ide help' to list all commandlets). val.edition=The tool edition. val.plugin=The plugin to select +val.recipe-extra-arguments=possible additional arguments for the recipe val.settingsRepository=The settings git repository with the IDEasy configuration for the project. val.tool=The tool commandlet to select. val.version=The tool version. diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index 7944c5f23d..8abaa54cac 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -88,6 +88,9 @@ cmd.python=Werkzeug Kommando für Python. cmd.python.detail=Python ist eine objektorientierte Programmiersprache, vergleichbar mit Perl, Ruby, Scheme oder Java. Detaillierte Dokumentation ist zu finden unter https://www.python.org/doc/ cmd.quarkus=Werkzeug Kommando für Quarkus (Framework für Cloud-native Anwendungen). cmd.quarkus.detail=Quarkus ist ein Kubernetes-native Java-Framework zur Entwicklung von Cloud-native Anwendungen. Detaillierte Dokumentation ist zu finden unter https://quarkus.io/ +cmd.refactor=Refaktorieren Sie die vorhandene Codebasis mit spezifischen Rezepten von OpenRewrite +cmd.refactor.detail=OpenRewrite ist ein beliebtes Tool für Refactoring (Metaprogrammierung). Eine ausführliche Dokumentation finden Sie unter https://docs.openrewrite.org/ +cmd.refactor.val.recipe_name=Rezeptnamen umgestalten (RECIPE_1|RECIPE_2|RECIPE_3) cmd.repository=Richtet das vorkonfigurierte Git Repository ein mittels 'ide repository setup '. cmd.repository.detail=Dies wird alle vorkonfigurierten Repositories einrichten. Rufen Sie einfach 'ide repository setup ' auf, ersetzen Sie durch den Namen Ihrer Projektkonfigurationsdatei, die sich in 'settings/repository/your_project_name' befindet und IDEasy wird Ihr Projekt basierend auf der vorhandenen Eigenschaftsdatei automatisch klonen, bauen und einrichten.\nWenn Sie den Projektnamen weglassen, werden alle im Repository-Verzeichnis gefundenen Projekte vorkonfiguriert. cmd.repository.val.repository=Der Name der Properties-Datei des vorkonfigurierten Git Repositories zum Einrichten. Falls nicht angegeben, werden alle aktiven Projekte eingerichtet. @@ -155,6 +158,7 @@ val.cfg=Auswahl der Konfigurationsdatei (settings | home | conf | workspace). val.commandlet=Das ausgewählte Commandlet ("ide help" verwenden, um alle Commandlets aufzulisten). val.edition=Die Werkzeug Edition. val.plugin=Die zu selektierende Erweiterung. +val.recipe-extra-arguments=Mögliche zusätzliche Argumente für das Rezept. val.settingsRepository=Das settings git Repository mit den IDEasy Einstellungen für das Projekt. val.tool=Das zu selektierende Werkzeug Kommando. val.version=Die Werkzeug Version. diff --git a/cli/src/main/resources/refactor/openrewrite.json b/cli/src/main/resources/refactor/openrewrite.json new file mode 100644 index 0000000000..4ed6375340 --- /dev/null +++ b/cli/src/main/resources/refactor/openrewrite.json @@ -0,0 +1,16 @@ +[ + { + "origin_name": "java.format_java_code", + "ideasy_command": "FORMAT_JAVA_CODE", + "description": "Format Java code using a standard comprehensive set of Java formatting recipes.", + "url": "https://docs.openrewrite.org/recipes/java/format/autoformat", + "raw_cmd": "mvn -U org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.activeRecipes=org.openrewrite.java.format.AutoFormat -Drewrite.exportDatatables=true" + }, + { + "origin_name": "java.remove_blank_lines", + "ideasy_command": "REMOVE_BLANK_LINES", + "description": "Add and/or remove blank lines.", + "url": "https://docs.openrewrite.org/recipes/java/format/blanklines", + "raw_cmd": "mvn -U org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.activeRecipes=org.openrewrite.java.format.BlankLines -Drewrite.exportDatatables=true" + } +] diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/openrewrite/RecipeManagerTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/openrewrite/RecipeManagerTest.java new file mode 100644 index 0000000000..6710f0b767 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/openrewrite/RecipeManagerTest.java @@ -0,0 +1,37 @@ +package com.devonfw.tools.ide.tool.openrewrite; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +class RecipeManagerTest { + + static RecipeManager manager; + + @BeforeAll + static void init() { + manager = new RecipeManager(); + } + + @Test + public void testCreation() { + assertFalse(manager.listAvailableRecipes().isEmpty()); + } + + @Test + public void testStringValidation() { + assertFalse(manager.isValidRecipeNameRawName("NONSENSE")); + assertTrue(manager.isValidRecipeNameRawName(manager.listAvailableRecipes().stream().findAny().get().origin_name)); + } + + @Test + public void testEnumValidation() { + assertFalse(manager.isValidRecipeEnum(RefactorRecipeEnum.UNRECOGNIZED_RECIPE)); + assertTrue(manager.isValidRecipeEnum( + Arrays.stream(RefactorRecipeEnum.values()) + .filter(x -> !x.equals(RefactorRecipeEnum.UNRECOGNIZED_RECIPE)).findAny().get())); + } +} diff --git a/cli/src/test/resources/refactor/openrewrite.json b/cli/src/test/resources/refactor/openrewrite.json new file mode 100644 index 0000000000..4ed6375340 --- /dev/null +++ b/cli/src/test/resources/refactor/openrewrite.json @@ -0,0 +1,16 @@ +[ + { + "origin_name": "java.format_java_code", + "ideasy_command": "FORMAT_JAVA_CODE", + "description": "Format Java code using a standard comprehensive set of Java formatting recipes.", + "url": "https://docs.openrewrite.org/recipes/java/format/autoformat", + "raw_cmd": "mvn -U org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.activeRecipes=org.openrewrite.java.format.AutoFormat -Drewrite.exportDatatables=true" + }, + { + "origin_name": "java.remove_blank_lines", + "ideasy_command": "REMOVE_BLANK_LINES", + "description": "Add and/or remove blank lines.", + "url": "https://docs.openrewrite.org/recipes/java/format/blanklines", + "raw_cmd": "mvn -U org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.activeRecipes=org.openrewrite.java.format.BlankLines -Drewrite.exportDatatables=true" + } +]