From f2668e5641e0abf021b84e796ac1a4109c3a3305 Mon Sep 17 00:00:00 2001 From: aboyko Date: Mon, 3 Apr 2023 18:26:51 -0400 Subject: [PATCH] Rework loading of rewrite recipes and code actions --- .../boot/ls/PlugRecipesPreferencePage.java | 7 +- .../rewrite/config/CodeActionRepoLoader.java | 80 +++++++ .../config/StsClasspathScanningLoader.java | 217 ------------------ .../rewrite/config/StsEnvironment.java | 95 ++++---- .../java/rewrite/RewriteRecipeRepository.java | 25 +- 5 files changed, 134 insertions(+), 290 deletions(-) create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/CodeActionRepoLoader.java delete mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsClasspathScanningLoader.java diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/PlugRecipesPreferencePage.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/PlugRecipesPreferencePage.java index 4df8adf37..19c484a5d 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/PlugRecipesPreferencePage.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/PlugRecipesPreferencePage.java @@ -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 @@ -13,7 +13,6 @@ package org.springframework.tooling.boot.ls; import java.util.List; import org.eclipse.jface.preference.FieldEditorPreferencePage; -import org.eclipse.jface.preference.PathEditor; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPreferencePage; @@ -33,8 +32,8 @@ public class PlugRecipesPreferencePage extends FieldEditorPreferencePage impleme 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)); +// addField(new PathEditor(Constants.PREF_REWRITE_RECIPES_SCAN_DIRS, "Directories to scan for Recipes", +// "Select directory to scan for Recipes", fieldEditorParent)); } diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/CodeActionRepoLoader.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/CodeActionRepoLoader.java new file mode 100644 index 000000000..c574e47a5 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/CodeActionRepoLoader.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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.commons.rewrite.config; + +import java.lang.reflect.Constructor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.openrewrite.internal.RecipeIntrospectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ScanResult; + +public class CodeActionRepoLoader { + + final static Logger log = LoggerFactory.getLogger(CodeActionRepoLoader.class); + + private final List codeActionRepos = new ArrayList<>(); + + public CodeActionRepoLoader(String... acceptPackages) { + scanClasses(new ClassGraph().acceptPackages(acceptPackages), getClass().getClassLoader()); + } + + public CodeActionRepoLoader(Path p, ClassLoader classLoader) { + if (Files.isDirectory(p)) { + String dir = p.toString(); + + scanClasses(new ClassGraph().acceptPaths(dir).ignoreParentClassLoaders().overrideClassLoaders(classLoader), + classLoader); + + } else { + String jarName = p.toFile().getName(); + + scanClasses( + new ClassGraph().acceptJars(jarName).ignoreParentClassLoaders().overrideClassLoaders(classLoader), + classLoader); + + } + } + + private void scanClasses(ClassGraph classGraph, ClassLoader classLoader) { + try (ScanResult result = classGraph.ignoreClassVisibility().overrideClassLoaders(classLoader).scan()) { + + for (ClassInfo classInfo : result.getSubclasses(CodeActionRepository.class.getName())) { + Class codeActionRepoClass = classInfo.loadClass(); + Constructor primaryConstructor = RecipeIntrospectionUtils + .getZeroArgsConstructor(codeActionRepoClass); + if (primaryConstructor == null) { + // TODO: error!!! + } else { + try { + CodeActionRepository repo = (CodeActionRepository) primaryConstructor.newInstance(); + codeActionRepos.add(repo); + } catch (Throwable t) { + log.warn("Unable to configure " + codeActionRepoClass.getName(), t); + } + } + + } + } + } + + public List listCodeActionDescriptorsRepositories() { + return codeActionRepos; + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsClasspathScanningLoader.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsClasspathScanningLoader.java deleted file mode 100644 index 0018efee0..000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsClasspathScanningLoader.java +++ /dev/null @@ -1,217 +0,0 @@ -/******************************************************************************* - * 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 - * https://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VMware, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.commons.rewrite.config; - -import static java.util.Collections.emptyList; -import static org.openrewrite.internal.RecipeIntrospectionUtils.constructRecipe; - -import java.lang.reflect.Constructor; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import javax.annotation.Nullable; - -import org.openrewrite.Contributor; -import org.openrewrite.Recipe; -import org.openrewrite.config.CategoryDescriptor; -import org.openrewrite.config.DeclarativeRecipe; -import org.openrewrite.config.RecipeDescriptor; -import org.openrewrite.config.RecipeExample; -import org.openrewrite.config.ResourceLoader; -import org.openrewrite.config.YamlResourceLoader; -import org.openrewrite.internal.RecipeIntrospectionUtils; -import org.openrewrite.style.NamedStyles; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.github.classgraph.ClassGraph; -import io.github.classgraph.ClassInfo; -import io.github.classgraph.ScanResult; - -public class StsClasspathScanningLoader implements ResourceLoader, StsResourceLoader { - - final static Logger log = LoggerFactory.getLogger(StsClasspathScanningLoader.class); - - private final List recipes = new ArrayList<>(); - private final List styles = new ArrayList<>(); - - private final List recipeDescriptors = new ArrayList<>(); - private final List categoryDescriptors = new ArrayList<>(); - private final Map> recipeAttributions = new HashMap<>(); - private final Map> recipeExamples = new HashMap<>(); - - private final List codeActionRepos = new ArrayList<>(); - - public StsClasspathScanningLoader(Path p, Properties properties, Collection dependencyResourceLoaders, ClassLoader classLoader) { - if (Files.isDirectory(p)) { - String dir = p.toString(); - - scanClasses(new ClassGraph() - .acceptPaths(dir) - .ignoreParentClassLoaders() - .overrideClassLoaders(classLoader), classLoader); - - scanYaml(new ClassGraph() - .acceptPaths(dir) - .ignoreParentClassLoaders() - .overrideClassLoaders(classLoader) - .acceptPaths("META-INF/rewrite"), properties, dependencyResourceLoaders, classLoader); - - } else { - String jarName = p.toFile().getName(); - - scanClasses(new ClassGraph() - .acceptJars(jarName) - .ignoreParentClassLoaders() - .overrideClassLoaders(classLoader), classLoader); - - scanYaml(new ClassGraph() - .acceptJars(jarName) - .ignoreParentClassLoaders() - .overrideClassLoaders(classLoader) - .acceptPaths("META-INF/rewrite"), properties, dependencyResourceLoaders, classLoader); - } - - } - - public StsClasspathScanningLoader(Properties properties, String[] acceptPackages) { - scanClasses(new ClassGraph().acceptPackages(acceptPackages), getClass().getClassLoader()); - scanYaml(new ClassGraph().acceptPaths("META-INF/rewrite"), properties, emptyList(), null); - } - - /** - * Construct a ClasspathScanningLoader scans the provided classload for recipes - * - * @param properties Yaml placeholder properties - * @param classLoader Limit scan to classes loadable by this classloader - */ - public StsClasspathScanningLoader(Properties properties, ClassLoader classLoader) { - scanClasses(new ClassGraph() - .ignoreParentClassLoaders() - .overrideClassLoaders(classLoader), classLoader); - - scanYaml(new ClassGraph() - .ignoreParentClassLoaders() - .overrideClassLoaders(classLoader) - .acceptPaths("META-INF/rewrite"), properties, emptyList(), classLoader); - } - - /** - * This must be called _after_ scanClasses or the descriptors of declarative recipes will be missing any - * non-declarative recipes they depend on that would be discovered by scanClasses - */ - private void scanYaml(ClassGraph classGraph, Properties properties, Collection dependencyResourceLoaders, @Nullable ClassLoader classLoader) { - try (ScanResult scanResult = classGraph.enableMemoryMapping().scan()) { - List yamlResourceLoaders = new ArrayList<>(); - - scanResult.getResourcesWithExtension("yml").forEachInputStreamIgnoringIOException((res, input) -> { - yamlResourceLoaders.add(new YamlResourceLoader(input, res.getURI(), properties, classLoader, dependencyResourceLoaders)); - }); - // Extract in two passes so that the full list of recipes from all sources are known when computing recipe descriptors - // Otherwise recipes which include recipes from other sources in their recipeList will have incomplete descriptors - for(YamlResourceLoader resourceLoader : yamlResourceLoaders) { - recipes.addAll(resourceLoader.listRecipes()); - categoryDescriptors.addAll(resourceLoader.listCategoryDescriptors()); - styles.addAll(resourceLoader.listStyles()); - recipeAttributions.putAll(resourceLoader.listContributors()); - recipeExamples.putAll(resourceLoader.listRecipeExamples()); - } - for(YamlResourceLoader resourceLoader : yamlResourceLoaders) { - recipeDescriptors.addAll(resourceLoader.listRecipeDescriptors(recipes, recipeAttributions, recipeExamples)); - } - } - } - - private void scanClasses(ClassGraph classGraph, ClassLoader classLoader) { - try (ScanResult result = classGraph - .ignoreClassVisibility() - .overrideClassLoaders(classLoader) - .scan()) { - - for (ClassInfo classInfo : result.getSubclasses(Recipe.class.getName())) { - Class recipeClass = classInfo.loadClass(); - if (recipeClass.getName().equals(DeclarativeRecipe.class.getName()) || recipeClass.getEnclosingClass() != null) { - continue; - } - try { - Recipe recipe = constructRecipe(recipeClass); - recipeDescriptors.add(recipe.getDescriptor()); - recipes.add(recipe); - } catch (Throwable t) { - log.warn("Unable to configure " + recipeClass.getName(), t); - } - } - for (ClassInfo classInfo : result.getSubclasses(NamedStyles.class.getName())) { - Class styleClass = classInfo.loadClass(); - try { - Constructor constructor = RecipeIntrospectionUtils.getZeroArgsConstructor(styleClass); - if(constructor != null) { - constructor.setAccessible(true); - styles.add((NamedStyles) constructor.newInstance()); - } - } catch (Throwable t) { - log.warn("Unable to configure " + styleClass.getName(), t); - } - } - - for (ClassInfo classInfo : result.getSubclasses(CodeActionRepository.class.getName())) { - Class codeActionRepoClass = classInfo.loadClass(); - Constructor primaryConstructor = RecipeIntrospectionUtils.getZeroArgsConstructor(codeActionRepoClass); - if (primaryConstructor == null) { - //TODO: error!!! - } else { - try { - CodeActionRepository repo = (CodeActionRepository) primaryConstructor.newInstance(); - codeActionRepos.add(repo); - } catch (Throwable t) { - log.warn("Unable to configure " + codeActionRepoClass.getName(), t); - } - } - - } - } - } - - @Override - public Collection listRecipes() { - return recipes; - } - - @Override - public Collection listRecipeDescriptors() { - return recipeDescriptors; - } - - @Override - public Collection listCategoryDescriptors() { - return categoryDescriptors; - } - - @Override - public Collection listStyles() { - return styles; - } - - public Map> listRecipeExamples() { - return recipeExamples; - } - - public List listCodeActionDescriptorsRepositories() { - return codeActionRepos; - } - -} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsEnvironment.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsEnvironment.java index be9087705..53c4a28e7 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsEnvironment.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsEnvironment.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022, 2023 VMware, Inc. + * 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 @@ -10,89 +10,72 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.rewrite.config; -import java.lang.reflect.Field; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.openrewrite.config.ClasspathScanningLoader; +import org.openrewrite.Recipe; import org.openrewrite.config.Environment; +import org.openrewrite.config.RecipeDescriptor; import org.openrewrite.config.ResourceLoader; -import static java.util.Collections.emptyList; +public class StsEnvironment { -public class StsEnvironment extends Environment { - - final private Supplier> codeActionRepos; + final private Supplier> codeActionRepos; + final private Environment env; - public StsEnvironment(Collection resourceLoaders) { - super(resourceLoaders); - codeActionRepos = () -> resourceLoaders.stream().filter(StsResourceLoader.class::isInstance).map(StsResourceLoader.class::cast).flatMap(l -> l.listCodeActionDescriptorsRepositories().stream()); + private StsEnvironment(Environment env, List loaders) { + this.env = env; + codeActionRepos = () -> loaders.stream().flatMap(l -> l.listCodeActionDescriptorsRepositories().stream()); } - - public static class Builder extends Environment.Builder { - - final private Properties props; - public Builder(Properties properties) { - super(properties); - this.props = properties; - } + public static class Builder { + + private List loaders = new ArrayList<>(); + private Environment.Builder envBuilder = new Environment.Builder(new Properties());; - @Override public Builder scanRuntimeClasspath(String... acceptPackages) { - return (Builder) load(new StsClasspathScanningLoader(props, acceptPackages)); + loaders.add(new CodeActionRepoLoader(acceptPackages)); + envBuilder.scanRuntimeClasspath(acceptPackages); + return this; } - @Override - public Builder scanClassLoader(ClassLoader classLoader) { - return (Builder) load(new StsClasspathScanningLoader(props, classLoader)); + public Builder scanJar(Path jar, ClassLoader classLoader) { + loaders.add(new CodeActionRepoLoader(jar, classLoader)); + envBuilder.scanJar(jar, Collections.emptyList(), classLoader); + return this; } - @Override - public Builder scanJar(Path jar, Collection dependencies, ClassLoader classLoader) { - List list = new ArrayList<>(); - for (Path dep : dependencies) { - ClasspathScanningLoader classpathScanningLoader = new ClasspathScanningLoader(dep, props, emptyList(), classLoader); - list.add(classpathScanningLoader); - } - return (Builder) load(new StsClasspathScanningLoader(jar, props, list, classLoader), list); - } - - public Builder scanPath(Path dir, Collection dependencies, ClassLoader classLoader) { - List list = new ArrayList<>(); - for (Path dep : dependencies) { - ClasspathScanningLoader classpathScanningLoader = new ClasspathScanningLoader(dep, props, emptyList(), classLoader); - list.add(classpathScanningLoader); - } - return (Builder) load(new StsClasspathScanningLoader(dir, props, list, classLoader)); - } - - @SuppressWarnings("unchecked") public StsEnvironment build() { - try { - Field f = Environment.Builder.class.getDeclaredField("resourceLoaders"); - f.setAccessible(true); - return new StsEnvironment((Collection) f.get(this)); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } + return new StsEnvironment(envBuilder.build(), loaders); + } + + public void load(ResourceLoader loader) { + envBuilder.load(loader, Collections.emptyList()); + } - } - + public List listCodeActionDescriptors() { return codeActionRepos.get().flatMap(r -> r.getCodeActionDescriptors().stream()).collect(Collectors.toList()); } - public static Builder builder() { - return new Builder(new Properties()); - } + public Collection listRecipes() { + return env.listRecipes(); + } + + public Collection listRecipeDescriptors() { + return env.listRecipeDescriptors(); + } + + public static Builder builder() { + return new Builder(); + } } 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 6b7747c24..650609b2c 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 @@ -15,7 +15,6 @@ import java.io.File; import java.io.FileInputStream; import java.net.URL; import java.net.URLClassLoader; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; @@ -249,7 +248,7 @@ public class RewriteRecipeRepository implements ApplicationContextAware { if (pathStr.endsWith(".jar")) { URLClassLoader classLoader = new URLClassLoader(new URL[] { f.toUri().toURL() }, getClass().getClassLoader()); - builder.scanJar(f, Collections.emptyList(), classLoader); + builder.scanJar(f, classLoader); } else if (pathStr.endsWith(".yml") || pathStr.endsWith(".yaml")) { builder.load(new YamlResourceLoader(new FileInputStream(f.toFile()), f.toUri(), new Properties())); } @@ -257,17 +256,17 @@ public class RewriteRecipeRepository implements ApplicationContextAware { log.error("Skipping folder " + p, e); } } - for (String p : scanDirs) { - try { - Path d = Path.of(p); - if (Files.isDirectory(d)) { - URLClassLoader classLoader = new URLClassLoader(new URL[] { d.toUri().toURL()}, getClass().getClassLoader()); - builder.scanPath(d, Collections.emptyList(), classLoader); - } - } catch (Exception e) { - log.error("Skipping folder " + p, e); - } - } +// for (String p : scanDirs) { +// try { +// Path d = Path.of(p); +// if (Files.isDirectory(d)) { +// URLClassLoader classLoader = new URLClassLoader(new URL[] { d.toUri().toURL()}, getClass().getClassLoader()); +// builder.scanPath(d, Collections.emptyList(), classLoader); +// } +// } catch (Exception e) { +// log.error("Skipping folder " + p, e); +// } +// } return builder.build(); }