All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.config.ClasspathScanningLoader Maven / Gradle / Ivy

There is a newer version: 8.40.2
Show newest version
/*
 * Copyright 2020 the original author or authors.
 * 

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* https://www.apache.org/licenses/LICENSE-2.0 *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.openrewrite.config; import io.github.classgraph.ClassGraph; import io.github.classgraph.ClassInfo; import io.github.classgraph.ScanResult; import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Timer; import org.jspecify.annotations.Nullable; import org.openrewrite.Contributor; import org.openrewrite.Recipe; import org.openrewrite.ScanningRecipe; import org.openrewrite.internal.MetricsHelper; import org.openrewrite.internal.RecipeIntrospectionUtils; import org.openrewrite.style.NamedStyles; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.nio.file.Path; import java.util.*; import static java.util.Collections.emptyList; import static org.openrewrite.internal.RecipeIntrospectionUtils.constructRecipe; public class ClasspathScanningLoader implements ResourceLoader { private final LinkedHashSet recipes = new LinkedHashSet<>(); private final List styles = new ArrayList<>(); private final LinkedHashSet recipeDescriptors = new LinkedHashSet<>(); private final List categoryDescriptors = new ArrayList<>(); private final Map> recipeAttributions = new HashMap<>(); private final Map> recipeExamples = new HashMap<>(); /** * Construct a ClasspathScanningLoader scans the runtime classpath of the current java process for recipes * * @param properties Yaml placeholder properties * @param acceptPackages Limit scan to specified packages */ public ClasspathScanningLoader(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 ClasspathScanningLoader(Properties properties, ClassLoader classLoader) { scanClasses(new ClassGraph() .ignoreParentClassLoaders() .overrideClassLoaders(classLoader), classLoader); scanYaml(new ClassGraph() .ignoreParentClassLoaders() .overrideClassLoaders(classLoader) .acceptPaths("META-INF/rewrite"), properties, emptyList(), classLoader); } public ClasspathScanningLoader(Path jar, Properties properties, Collection dependencyResourceLoaders, ClassLoader classLoader) { String jarName = jar.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 static ClasspathScanningLoader onlyYaml(Properties properties) { ClasspathScanningLoader classpathScanningLoader = new ClasspathScanningLoader(); classpathScanningLoader.scanYaml(new ClassGraph().acceptPaths("META-INF/rewrite"), properties, emptyList(), null); return classpathScanningLoader; } private ClasspathScanningLoader() { } /** * 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))); scanResult.getResourcesWithExtension("yaml").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()) { configureRecipes(result, Recipe.class.getName()); configureRecipes(result, ScanningRecipe.class.getName()); for (ClassInfo classInfo : result.getSubclasses(NamedStyles.class.getName())) { Class styleClass = classInfo.loadClass(); Constructor constructor = RecipeIntrospectionUtils.getZeroArgsConstructor(styleClass); if (constructor != null) { constructor.setAccessible(true); try { styles.add((NamedStyles) constructor.newInstance()); } catch (Exception e) { throw new RuntimeException(e); } } } } } private void configureRecipes(ScanResult result, String className) { for (ClassInfo classInfo : result.getSubclasses(className)) { Class recipeClass = classInfo.loadClass(); if (recipeClass.getName().equals(DeclarativeRecipe.class.getName()) || (recipeClass.getModifiers() & Modifier.PUBLIC) == 0 || // `ScanningRecipe` is an example of an abstract `Recipe` subtype (recipeClass.getModifiers() & Modifier.ABSTRACT) != 0) { continue; } Timer.Builder builder = Timer.builder("rewrite.scan.configure.recipe"); Timer.Sample sample = Timer.start(); try { Recipe recipe = constructRecipe(recipeClass); recipeDescriptors.add(recipe.getDescriptor()); recipes.add(recipe); MetricsHelper.successTags(builder.tags("recipe", "elided")); } catch (Throwable e) { MetricsHelper.errorTags(builder.tags("recipe", recipeClass.getName()), e); } finally { sample.stop(builder.register(Metrics.globalRegistry)); } } } @Override public Collection listRecipes() { return recipes; } @Override public Collection listRecipeDescriptors() { return recipeDescriptors; } @Override public Collection listCategoryDescriptors() { return categoryDescriptors; } @Override public Collection listStyles() { return styles; } @Override public Map> listRecipeExamples() { return recipeExamples; } @Override public Map> listContributors() { return recipeAttributions; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy