Skip to content

Commit 101e384

Browse files
authored
Utilize a child-first strategy for resource loading (#6414)
* Utilize a child-first strategy for resource loading * Add override annotations as desired by the review-bot
1 parent 444d0a0 commit 101e384

File tree

2 files changed

+145
-3
lines changed

2 files changed

+145
-3
lines changed

rewrite-core/src/main/java/org/openrewrite/marketplace/RecipeClassLoader.java

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@
1717

1818
import org.jspecify.annotations.Nullable;
1919

20+
import java.io.IOException;
2021
import java.io.UncheckedIOException;
2122
import java.net.MalformedURLException;
2223
import java.net.URL;
2324
import java.net.URLClassLoader;
2425
import java.nio.file.Path;
25-
import java.util.Arrays;
26-
import java.util.List;
27-
import java.util.Objects;
26+
import java.util.*;
2827
import java.util.stream.Stream;
2928

3029
import static java.util.Collections.emptyList;
@@ -129,6 +128,27 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
129128
}
130129
}
131130

131+
@Override
132+
public @Nullable URL getResource(String name) {
133+
Objects.requireNonNull(name);
134+
URL url = findResource(name);
135+
if (url == null) {
136+
return parent.getResource(name);
137+
}
138+
return url;
139+
}
140+
141+
@Override
142+
public Enumeration<URL> getResources(String name) throws IOException {
143+
Objects.requireNonNull(name);
144+
@SuppressWarnings("unchecked")
145+
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
146+
tmp[0] = findResources(name);
147+
tmp[1] = parent.getResources(name);
148+
149+
return new CompoundEnumeration<>(tmp);
150+
}
151+
132152
private boolean shouldDelegateToParent(String className) {
133153
// Check additional delegations first (can be overridden)
134154
for (String prefix : getAdditionalParentDelegatedPackages()) {
@@ -208,3 +228,39 @@ public static URL[] getUrls(@Nullable Path recipeJar, List<Path> classpath) {
208228
.toArray(URL[]::new);
209229
}
210230
}
231+
232+
/*
233+
* A utility class that will enumerate over an array of enumerations.
234+
* @see java.lang.CompoundEnumeration
235+
*/
236+
final class CompoundEnumeration<E> implements Enumeration<E> {
237+
private final Enumeration<E>[] enums;
238+
private int index;
239+
240+
public CompoundEnumeration(Enumeration<E>[] enums) {
241+
this.enums = enums;
242+
}
243+
244+
private boolean next() {
245+
while (index < enums.length) {
246+
if (enums[index] != null && enums[index].hasMoreElements()) {
247+
return true;
248+
}
249+
index++;
250+
}
251+
return false;
252+
}
253+
254+
@Override
255+
public boolean hasMoreElements() {
256+
return next();
257+
}
258+
259+
@Override
260+
public E nextElement() {
261+
if (!next()) {
262+
throw new NoSuchElementException();
263+
}
264+
return enums[index].nextElement();
265+
}
266+
}

rewrite-core/src/test/java/org/openrewrite/marketplace/RecipeClassLoaderTest.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,22 @@
1616
package org.openrewrite.marketplace;
1717

1818
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.io.TempDir;
1920
import org.openrewrite.Recipe;
2021
import org.openrewrite.config.ClasspathScanningLoader;
2122
import org.openrewrite.config.Environment;
2223
import org.openrewrite.config.RecipeDescriptor;
24+
import org.openrewrite.internal.StringUtils;
2325

2426
import java.io.File;
27+
import java.io.IOException;
28+
import java.net.URL;
29+
import java.net.URLClassLoader;
30+
import java.nio.file.Files;
2531
import java.nio.file.Path;
2632
import java.nio.file.Paths;
2733
import java.util.ArrayList;
34+
import java.util.Enumeration;
2835
import java.util.List;
2936
import java.util.Properties;
3037

@@ -77,4 +84,83 @@ void loadRecipeWithIsolatedClassLoader() {
7784
.as("Recipe should be loaded through RecipeClassLoader for isolation")
7885
.isInstanceOf(RecipeClassLoader.class);
7986
}
87+
88+
@Test
89+
void resourcesShouldBeChildLoaded(@TempDir Path tempDir) throws IOException {
90+
Path lib1 = tempDir.resolve("lib1");
91+
Files.createDirectories(lib1);
92+
Path file1 = lib1.resolve("rewrite.txt");
93+
Files.write(file1, "file1".getBytes());
94+
Path lib2 = tempDir.resolve("lib2");
95+
Files.createDirectories(lib2);
96+
Path file2 = lib2.resolve("rewrite.txt");
97+
Files.write(file2, "file2".getBytes());
98+
99+
try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{lib1.toUri().toURL()})) {
100+
try (RecipeClassLoader classLoader = new RecipeClassLoader(new URL[]{lib2.toUri().toURL()}, urlClassLoader)) {
101+
String text = StringUtils.readFully(classLoader.getResourceAsStream("rewrite.txt"));
102+
assertThat(text).isEqualTo("file2");
103+
}
104+
}
105+
}
106+
107+
@Test
108+
void resourcesShouldFindFromParentLast(@TempDir Path tempDir) throws IOException {
109+
Path lib1 = tempDir.resolve("lib1");
110+
Files.createDirectories(lib1);
111+
Path file1 = lib1.resolve("rewrite.txt");
112+
Files.write(file1, "file1".getBytes());
113+
Path lib2 = tempDir.resolve("lib2");
114+
Files.createDirectories(lib2);
115+
116+
try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{lib1.toUri().toURL()})) {
117+
try (RecipeClassLoader classLoader = new RecipeClassLoader(new URL[]{lib2.toUri().toURL()}, urlClassLoader)) {
118+
String text = StringUtils.readFully(classLoader.getResourceAsStream("rewrite.txt"));
119+
assertThat(text).isEqualTo("file1");
120+
}
121+
}
122+
}
123+
124+
@Test
125+
void allResourcesShouldFindFromChildFirst(@TempDir Path tempDir) throws IOException {
126+
Path lib1 = tempDir.resolve("lib1");
127+
Files.createDirectories(lib1);
128+
Path file1 = lib1.resolve("rewrite.txt");
129+
Files.write(file1, "file1".getBytes());
130+
Path lib2 = tempDir.resolve("lib2");
131+
Files.createDirectories(lib2);
132+
Path file2 = lib2.resolve("rewrite.txt");
133+
Files.write(file2, "file2".getBytes());
134+
135+
try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{lib1.toUri().toURL()})) {
136+
try (RecipeClassLoader classLoader = new RecipeClassLoader(new URL[]{lib2.toUri().toURL()}, urlClassLoader)) {
137+
Enumeration<URL> resources = classLoader.getResources("rewrite.txt");
138+
assertThat(resources.hasMoreElements()).isTrue();
139+
assertThat(resources.nextElement().toString()).contains("lib2/rewrite.txt");
140+
assertThat(resources.hasMoreElements()).isTrue();
141+
assertThat(resources.nextElement().toString()).contains("lib1/rewrite.txt");
142+
assertThat(resources.hasMoreElements()).isFalse();
143+
}
144+
}
145+
}
146+
147+
@Test
148+
void allResourcesShouldFindFromParentLast(@TempDir Path tempDir) throws IOException {
149+
Path lib1 = tempDir.resolve("lib1");
150+
Files.createDirectories(lib1);
151+
Path file1 = lib1.resolve("rewrite.txt");
152+
Files.write(file1, "file1".getBytes());
153+
Path lib2 = tempDir.resolve("lib2");
154+
Files.createDirectories(lib2);
155+
156+
try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{lib1.toUri().toURL()})) {
157+
try (RecipeClassLoader classLoader = new RecipeClassLoader(new URL[]{lib2.toUri().toURL()}, urlClassLoader)) {
158+
Enumeration<URL> resources = classLoader.getResources("rewrite.txt");
159+
assertThat(resources.hasMoreElements()).isTrue();
160+
assertThat(resources.nextElement().toString()).contains("lib1/rewrite.txt");
161+
assertThat(resources.hasMoreElements()).isFalse();
162+
163+
}
164+
}
165+
}
80166
}

0 commit comments

Comments
 (0)