|
29 | 29 | import org.openrewrite.internal.RecipeLoader; |
30 | 30 | import org.openrewrite.style.NamedStyles; |
31 | 31 |
|
| 32 | +import java.io.IOException; |
| 33 | +import java.io.UncheckedIOException; |
32 | 34 | import java.lang.reflect.Constructor; |
33 | 35 | import java.lang.reflect.Modifier; |
34 | | -import java.nio.file.Path; |
| 36 | +import java.nio.file.*; |
| 37 | +import java.nio.file.attribute.BasicFileAttributes; |
35 | 38 | import java.util.*; |
| 39 | +import java.util.jar.JarEntry; |
| 40 | +import java.util.jar.JarOutputStream; |
36 | 41 |
|
| 42 | +import static java.nio.file.Files.*; |
37 | 43 | import static java.util.Collections.emptyList; |
38 | 44 | import static java.util.Collections.emptyMap; |
39 | 45 |
|
@@ -97,26 +103,73 @@ public ClasspathScanningLoader(Properties properties, ClassLoader classLoader) { |
97 | 103 | /** |
98 | 104 | * Construct a ClasspathScanningLoader as used from `Environment.scanJar` for |
99 | 105 | * `MavenRecipeBundleReader.marketplaceFromClasspathScan`. |
| 106 | + * Supports both jar files and directories containing class files. |
100 | 107 | */ |
101 | 108 | public ClasspathScanningLoader(Path jar, Properties properties, Collection<? extends ResourceLoader> dependencyResourceLoaders, ClassLoader classLoader) { |
102 | 109 | this.classLoader = classLoader; |
103 | 110 | this.recipeLoader = new RecipeLoader(classLoader); |
104 | | - String jarName = jar.toFile().getName(); |
105 | 111 |
|
106 | 112 | this.performScan = () -> { |
107 | | - scanClasses(new ClassGraph() |
| 113 | + Path jarPath; |
| 114 | + if (isDirectory(jar)) { |
| 115 | + try { |
| 116 | + jarPath = createTempJarFromDirectory(jar); |
| 117 | + } catch (IOException e) { |
| 118 | + throw new UncheckedIOException("Failed to create temporary jar from directory: " + jar, e); |
| 119 | + } |
| 120 | + } else { |
| 121 | + jarPath = jar; |
| 122 | + } |
| 123 | + |
| 124 | + String jarName = jarPath.toFile().getName(); |
| 125 | + ClassGraph classGraph = new ClassGraph() |
| 126 | + .overrideClasspath(jarPath.toString()) |
108 | 127 | .acceptJars(jarName) |
109 | | - .ignoreParentClassLoaders() |
110 | | - .overrideClassLoaders(classLoader), classLoader); |
| 128 | + .overrideClassLoaders(classLoader); |
111 | 129 |
|
112 | | - scanYaml(new ClassGraph() |
| 130 | + ClassGraph yamlGraph = new ClassGraph() |
| 131 | + .overrideClasspath(jarPath.toString()) |
113 | 132 | .acceptJars(jarName) |
114 | | - .ignoreParentClassLoaders() |
115 | 133 | .overrideClassLoaders(classLoader) |
116 | | - .acceptPaths("META-INF/rewrite"), properties, dependencyResourceLoaders, classLoader); |
| 134 | + .acceptPaths("META-INF/rewrite"); |
| 135 | + |
| 136 | + scanClasses(classGraph, classLoader); |
| 137 | + scanYaml(yamlGraph, properties, dependencyResourceLoaders, classLoader); |
117 | 138 | }; |
118 | 139 | } |
119 | 140 |
|
| 141 | + /** |
| 142 | + * Creates a temporary jar file containing all files from the given directory. |
| 143 | + */ |
| 144 | + private static Path createTempJarFromDirectory(Path directory) throws IOException { |
| 145 | + Path tempJar = createTempFile("recipe-scan-", ".jar"); |
| 146 | + tempJar.toFile().deleteOnExit(); |
| 147 | + |
| 148 | + try (JarOutputStream jos = new JarOutputStream(newOutputStream(tempJar))) { |
| 149 | + walkFileTree(directory, new SimpleFileVisitor<Path>() { |
| 150 | + @Override |
| 151 | + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { |
| 152 | + String entryName = directory.relativize(file).toString().replace('\\', '/'); |
| 153 | + jos.putNextEntry(new JarEntry(entryName)); |
| 154 | + copy(file, jos); |
| 155 | + jos.closeEntry(); |
| 156 | + return FileVisitResult.CONTINUE; |
| 157 | + } |
| 158 | + |
| 159 | + @Override |
| 160 | + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { |
| 161 | + if (!dir.equals(directory)) { |
| 162 | + String entryName = directory.relativize(dir).toString().replace('\\', '/') + "/"; |
| 163 | + jos.putNextEntry(new JarEntry(entryName)); |
| 164 | + jos.closeEntry(); |
| 165 | + } |
| 166 | + return FileVisitResult.CONTINUE; |
| 167 | + } |
| 168 | + }); |
| 169 | + } |
| 170 | + return tempJar; |
| 171 | + } |
| 172 | + |
120 | 173 | /** |
121 | 174 | * Construct a ClasspathScanningLoader to load Yaml categories and recipes from the runtime classpath, as part of |
122 | 175 | * running tests or inferring local recipe categories. |
|
0 commit comments