Rework loading of rewrite recipes and code actions
This commit is contained in:
@@ -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));
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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<CodeActionRepository> 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<CodeActionRepository> listCodeActionDescriptorsRepositories() {
|
||||
return codeActionRepos;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Recipe> recipes = new ArrayList<>();
|
||||
private final List<NamedStyles> styles = new ArrayList<>();
|
||||
|
||||
private final List<RecipeDescriptor> recipeDescriptors = new ArrayList<>();
|
||||
private final List<CategoryDescriptor> categoryDescriptors = new ArrayList<>();
|
||||
private final Map<String, List<Contributor>> recipeAttributions = new HashMap<>();
|
||||
private final Map<String, List<RecipeExample>> recipeExamples = new HashMap<>();
|
||||
|
||||
private final List<CodeActionRepository> codeActionRepos = new ArrayList<>();
|
||||
|
||||
public StsClasspathScanningLoader(Path p, Properties properties, Collection<? extends ResourceLoader> 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<? extends ResourceLoader> dependencyResourceLoaders, @Nullable ClassLoader classLoader) {
|
||||
try (ScanResult scanResult = classGraph.enableMemoryMapping().scan()) {
|
||||
List<YamlResourceLoader> 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<Recipe> listRecipes() {
|
||||
return recipes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<RecipeDescriptor> listRecipeDescriptors() {
|
||||
return recipeDescriptors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<CategoryDescriptor> listCategoryDescriptors() {
|
||||
return categoryDescriptors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<NamedStyles> listStyles() {
|
||||
return styles;
|
||||
}
|
||||
|
||||
public Map<String, List<RecipeExample>> listRecipeExamples() {
|
||||
return recipeExamples;
|
||||
}
|
||||
|
||||
public List<CodeActionRepository> listCodeActionDescriptorsRepositories() {
|
||||
return codeActionRepos;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Stream<CodeActionRepository>> codeActionRepos;
|
||||
final private Supplier<Stream<CodeActionRepository>> codeActionRepos;
|
||||
final private Environment env;
|
||||
|
||||
public StsEnvironment(Collection<? extends ResourceLoader> resourceLoaders) {
|
||||
super(resourceLoaders);
|
||||
codeActionRepos = () -> resourceLoaders.stream().filter(StsResourceLoader.class::isInstance).map(StsResourceLoader.class::cast).flatMap(l -> l.listCodeActionDescriptorsRepositories().stream());
|
||||
private StsEnvironment(Environment env, List<CodeActionRepoLoader> 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<CodeActionRepoLoader> 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<Path> dependencies, ClassLoader classLoader) {
|
||||
List<ClasspathScanningLoader> 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<Path> dependencies, ClassLoader classLoader) {
|
||||
List<ClasspathScanningLoader> 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<ResourceLoader>) 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<RecipeCodeActionDescriptor> listCodeActionDescriptors() {
|
||||
return codeActionRepos.get().flatMap(r -> r.getCodeActionDescriptors().stream()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder(new Properties());
|
||||
}
|
||||
public Collection<Recipe> listRecipes() {
|
||||
return env.listRecipes();
|
||||
}
|
||||
|
||||
public Collection<RecipeDescriptor> listRecipeDescriptors() {
|
||||
return env.listRecipeDescriptors();
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user