From f36090ddc3e6b1d7445a056390d9e2669c7c7db2 Mon Sep 17 00:00:00 2001 From: aboyko Date: Thu, 13 Oct 2022 17:18:16 -0400 Subject: [PATCH] Preference setting for white-listing recipes --- .../tooling/boot/ls/Constants.java | 2 + .../DelegatingStreamConnectionProvider.java | 2 + .../tooling/boot/ls/PrefsInitializer.java | 14 ++++ .../boot/ls/RewritePreferencePage.java | 14 +++- .../boot/ls/prefs/StringListEditor.java | 61 +++++++++++++++ .../ide/vscode/boot/app/BootJavaConfig.java | 4 + .../rewrite/RecipesDescriptionGenerator.java | 14 +++- .../java/rewrite/RewriteRecipeRepository.java | 75 +++++++++++++------ .../vscode-spring-boot/package.json | 19 +++++ 9 files changed, 177 insertions(+), 28 deletions(-) create mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/StringListEditor.java diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java index 4174ddb3f..81d61c981 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java @@ -33,6 +33,8 @@ public class Constants { public static final String PREF_REWRITE_RECONCILE = "boot-java.rewrite.reconcile"; + public static final String PREF_REWRITE_RECIPE_FILTERS = "boot-java.rewrite.recipe-filters"; + public static final String PREF_REWRITE_RECIPES_SCAN_FILES = "boot-java.rewrite.scan-files"; public static final String PREF_REWRITE_RECIPES_SCAN_DIRS = "boot-java.rewrite.scan-directories"; diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java index 2bfad7b87..7d0b7e1eb 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java @@ -35,6 +35,7 @@ import org.springframework.tooling.boot.ls.prefs.CategoryProblemsSeverityPrefsPa import org.springframework.tooling.boot.ls.prefs.FileListEditor; import org.springframework.tooling.boot.ls.prefs.ProblemCategoryData; import org.springframework.tooling.boot.ls.prefs.ProblemCategoryData.CategoryToggleData; +import org.springframework.tooling.boot.ls.prefs.StringListEditor; import org.springsource.ide.eclipse.commons.boot.ls.remoteapps.RemoteBootAppsDataHolder; import org.springsource.ide.eclipse.commons.boot.ls.remoteapps.RemoteBootAppsDataHolder.RemoteAppData; import org.springsource.ide.eclipse.commons.livexp.core.ValueListener; @@ -191,6 +192,7 @@ public class DelegatingStreamConnectionProvider implements StreamConnectionProvi bootJavaObj.put("rewrite", Map.of( "reconcile", preferenceStore.getBoolean(Constants.PREF_REWRITE_RECONCILE), + "recipe-filters", StringListEditor.decode(preferenceStore.getString(Constants.PREF_REWRITE_RECIPE_FILTERS)), "scan-directories", FileListEditor.getValuesFromPreference(preferenceStore.getString(Constants.PREF_REWRITE_RECIPES_SCAN_DIRS)), "scan-files", FileListEditor.getValuesFromPreference(preferenceStore.getString(Constants.PREF_REWRITE_RECIPES_SCAN_FILES)) )); diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/PrefsInitializer.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/PrefsInitializer.java index 4592d0a30..1cbccef29 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/PrefsInitializer.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/PrefsInitializer.java @@ -12,6 +12,7 @@ package org.springframework.tooling.boot.ls; import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; import org.eclipse.jface.preference.IPreferenceStore; +import org.springframework.tooling.boot.ls.prefs.StringListEditor; /** * Preferences initializer for Boot-Java LS extension @@ -42,6 +43,19 @@ public class PrefsInitializer extends AbstractPreferenceInitializer { preferenceStore.setDefault(Constants.PREF_SCAN_JAVA_TEST_SOURCES, false); preferenceStore.setDefault(Constants.PREF_REWRITE_RECONCILE, false); + + preferenceStore.setDefault(Constants.PREF_REWRITE_RECIPE_FILTERS, StringListEditor.encode(new String[] { + "org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration", + "org.openrewrite.java.spring.boot2.SpringBoot2BestPractices", + "org.openrewrite.java.spring.boot2.SpringBoot1To2Migration", + "org.openrewrite.java.testing.junit5.JUnit5BestPractices", + "org.openrewrite.java.testing.junit5.JUnit4to5Migration", + "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6", + "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", + "org.rewrite.java.security.*", + "org.springframework.rewrite.test.*", + "rewrite.test.*" + })); } } diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java index 06708f8cc..6c1a85640 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java @@ -9,6 +9,7 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPreferencePage; import org.springframework.tooling.boot.ls.prefs.FileListEditor; +import org.springframework.tooling.boot.ls.prefs.StringListEditor; public class RewritePreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { @@ -21,11 +22,18 @@ public class RewritePreferencePage extends FieldEditorPreferencePage implements protected void createFieldEditors() { Composite fieldEditorParent = getFieldEditorParent(); - addField(new BooleanFieldEditor(Constants.PREF_REWRITE_RECONCILE, "Experimental reconciling for Java source based on Rewrite project", fieldEditorParent)); + addField(new BooleanFieldEditor(Constants.PREF_REWRITE_RECONCILE, + "Experimental reconciling for Java source based on Rewrite project", fieldEditorParent)); - addField(new FileListEditor(Constants.PREF_REWRITE_RECIPES_SCAN_FILES, "JAR and YAML files to scan for Recipes", "Select JARs and YAML files:", fieldEditorParent, List.of("jar", "yml", "yaml"))); + addField(new StringListEditor(fieldEditorParent, Constants.PREF_REWRITE_RECIPE_FILTERS, + "Recipe filter IDs and patterns", "Filter Value", + "Either exact ID or pattern with '*' as the wild-card:", text -> text.isBlank() ? "Cannot be blank" : null)); - addField(new PathEditor(Constants.PREF_REWRITE_RECIPES_SCAN_DIRS, "Directories to scan for Recipes", "Select directory to scan for Recipes", fieldEditorParent)); + addField(new FileListEditor(Constants.PREF_REWRITE_RECIPES_SCAN_FILES, "JAR and YAML files to scan for Recipes", + "Select JARs and YAML files:", fieldEditorParent, List.of("jar", "yml", "yaml"))); + + addField(new PathEditor(Constants.PREF_REWRITE_RECIPES_SCAN_DIRS, "Directories to scan for Recipes", + "Select directory to scan for Recipes", fieldEditorParent)); } diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/StringListEditor.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/StringListEditor.java new file mode 100644 index 000000000..698dbc39f --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/StringListEditor.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2022 VMware, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.boot.ls.prefs; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.dialogs.InputDialog; +import org.eclipse.jface.preference.ListEditor; +import org.eclipse.swt.widgets.Composite; + +public class StringListEditor extends ListEditor { + + private static final String DELIMITER = "---"; + + private String promptTitle; + private String promptMessage; + private IInputValidator validator; + + public static String encode(String[] items) { + return String.join(DELIMITER, items); + } + + public static String[] decode(String value) { + return value.split(DELIMITER); + } + + @Override + protected String createList(String[] items) { + return encode(items); + } + + public StringListEditor(Composite parent, String name, String label, String promptTitle, String promptMessage, IInputValidator validator) { + super(name, label, parent); + this.promptTitle = promptTitle; + this.promptMessage = promptMessage; + this.validator = validator; + } + + @Override + protected String getNewInputObject() { + InputDialog dialog = new InputDialog(getShell(), promptTitle, promptMessage, "", validator); + if (dialog.open() == IDialogConstants.OK_ID) { + return dialog.getValue(); + } + return null; + } + + @Override + protected String[] parseString(String stringList) { + return decode(stringList); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java index fd562fcf3..e6c2d360c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java @@ -167,6 +167,10 @@ public class BootJavaConfig implements InitializingBean { public Set getRecipeDirectories() { return settings.getStringSet("boot-java", "rewrite", "scan-directories"); } + + public Set getRecipesFilters() { + return settings.getStringSet("boot-java", "rewrite", "recipe-filters"); + } public Set getRecipeFiles() { return settings.getStringSet("boot-java", "rewrite", "scan-files"); 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 index 97de96955..ce824433d 100644 --- 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 @@ -16,6 +16,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.Set; import java.util.stream.Collectors; import org.openrewrite.config.Environment; @@ -23,10 +24,21 @@ import org.openrewrite.config.OptionDescriptor; import org.openrewrite.config.RecipeDescriptor; public class RecipesDescriptionGenerator { + + private 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", + "org.openrewrite.java.testing.junit5.JUnit5BestPractices", + "org.openrewrite.java.testing.junit5.JUnit4to5Migration", + "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6", + "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0" + ); + 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())) + .filter(d -> TOP_LEVEL_RECIPES.contains(d.getName())) .map(d -> convertToMarkdown(d, 1)) .collect(Collectors.joining("\n\n")); 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 85466a712..06de40574 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 @@ -29,6 +29,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -107,17 +108,8 @@ public class RewriteRecipeRepository implements ApplicationContextAware { private Set scanFiles = Collections.emptySet(); private Set scanDirs = Collections.emptySet(); - - 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", - "org.openrewrite.java.testing.junit5.JUnit5BestPractices", - "org.openrewrite.java.testing.junit5.JUnit4to5Migration", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6", - "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0" - ); - + private Set recipeFilters = Collections.emptySet(); + private static Gson serializationGson = new GsonBuilder() .registerTypeAdapter(Duration.class, new DurationTypeConverter()) .create(); @@ -134,6 +126,7 @@ public class RewriteRecipeRepository implements ApplicationContextAware { server.doOnInitialized(() -> { this.scanDirs = config.getRecipeDirectories(); this.scanFiles = config.getRecipeFiles(); + this.recipeFilters = config.getRecipesFilters(); load().thenAccept(v -> registerCommands()); config.addListener(l -> { if (!scanDirs.equals(config.getRecipeDirectories()) @@ -144,6 +137,11 @@ public class RewriteRecipeRepository implements ApplicationContextAware { scanFiles = config.getRecipeFiles(); load(); } + Set recipeFilterFromConfig = config.getRecipesFilters(); + if (!recipeFilters.equals(recipeFilterFromConfig)) { + recipeFilters = recipeFilterFromConfig; + updateGlobalCommandRecipes(); + } }); }); } @@ -176,19 +174,9 @@ public class RewriteRecipeRepository implements ApplicationContextAware { } recipes.put(r.getName(), r); - if (TOP_LEVEL_RECIPES.contains(r.getName()) || r.getName().startsWith("rewrite.test.") - || r.getName().startsWith("org.rewrite.java.security") - || r.getName().startsWith("org.springframework.rewrite.test")) { - Validated validation = Validated.invalid(null, null, null); - try { - validation = r.validate(); - } catch (Exception e) { - // ignore - } - if (validation.isValid()) { - globalCommandRecipes.add(r); - } - } + if (isAcceptableGlobalCommandRecipe(r)) { + globalCommandRecipes.add(r); + } } } javaProblemDescriptors.addAll(env.listProblemDescriptors()); @@ -201,6 +189,45 @@ public class RewriteRecipeRepository implements ApplicationContextAware { } } + private void updateGlobalCommandRecipes() { + globalCommandRecipes.clear(); + for (Recipe r : recipes.values()) { + if (isAcceptableGlobalCommandRecipe(r)) { + globalCommandRecipes.add(r); + } + } + } + + private boolean isAcceptableGlobalCommandRecipe(Recipe r) { + for (String filter : recipeFilters) { + if (!filter.isBlank()) { + // Check if wild-card character present + if (filter.indexOf('*') < 0) { + // No wild-card - direct equality + if (filter.equals(r.getName())) { + return isRecipeValid(r); + } + } else { + // Wild-card present - convert to regular expression + if (Pattern.matches(filter.replaceAll("\\*", "\\.*"), r.getName())) { + return isRecipeValid(r); + } + } + } + } + return false; + } + + private static boolean isRecipeValid(Recipe r) { + Validated validation = Validated.invalid(null, null, null); + try { + validation = r.validate(); + } catch (Exception e) { + // ignore + } + return validation.isValid(); + } + private StsEnvironment createRewriteEnvironment() { StsEnvironment.Builder builder = (StsEnvironment.Builder) StsEnvironment.builder().scanRuntimeClasspath(); for (String p : scanFiles) { diff --git a/vscode-extensions/vscode-spring-boot/package.json b/vscode-extensions/vscode-spring-boot/package.json index 4a45c8032..f03437b76 100644 --- a/vscode-extensions/vscode-spring-boot/package.json +++ b/vscode-extensions/vscode-spring-boot/package.json @@ -198,6 +198,25 @@ "default": false, "description": "Experimental reconciling for Java source based on Rewrite project" }, + "boot-java.rewrite.recipe-filters": { + "type": "array", + "default": [ + "org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration", + "org.openrewrite.java.spring.boot2.SpringBoot2BestPractices", + "org.openrewrite.java.spring.boot2.SpringBoot1To2Migration", + "org.openrewrite.java.testing.junit5.JUnit5BestPractices", + "org.openrewrite.java.testing.junit5.JUnit4to5Migration", + "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6", + "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", + "org.rewrite.java.security.*", + "org.springframework.rewrite.test.*", + "rewrite.test.*" + ], + "items": { + "type": "string" + }, + "description": "Recipe ID filter patterns. Either exact ids or patterns with '*' as the wild-card" + }, "boot-java.rewrite.scan-files": { "type": "array", "default": [],