This is an automated email from the ASF dual-hosted git repository.
danny0405 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git
The following commit(s) were added to refs/heads/master by this push:
new 6c666ee57985 [HUDI-736] Simplify
ReflectionUtils#getTopLevelClassesInClasspath (#19784)
6c666ee57985 is described below
commit 6c666ee57985b4823ea1bfa8a0c2fd2fc4025cf7
Author: LeonxLJX <[email protected]>
AuthorDate: Tue Sep 8 12:17:20 2026 +0800
[HUDI-736] Simplify ReflectionUtils#getTopLevelClassesInClasspath (#19784)
* [HUDI-736] Simplify ReflectionUtils#getTopLevelClassesInClasspath
Replace the imperative loop with a stream pipeline over the package
resources and fix a latent NPE: if ClassLoader#getResources throws an
IOException, the previous code left 'resources' null and then called
Objects.requireNonNull on it. The simplified version returns an empty
stream on error instead.
* refactor(common): minimize classpath discovery changes against master
---------
Co-authored-by: LeonxLJX <[email protected]>
Co-authored-by: danny0405 <[email protected]>
---
.../hudi/common/util/TestReflectionUtils.java | 44 ++++++++++++++++++++++
.../apache/hudi/common/util/ReflectionUtils.java | 36 ++++++++++--------
2 files changed, 64 insertions(+), 16 deletions(-)
diff --git
a/hudi-common/src/test/java/org/apache/hudi/common/util/TestReflectionUtils.java
b/hudi-common/src/test/java/org/apache/hudi/common/util/TestReflectionUtils.java
index 01042c139133..7640435ae4c5 100644
---
a/hudi-common/src/test/java/org/apache/hudi/common/util/TestReflectionUtils.java
+++
b/hudi-common/src/test/java/org/apache/hudi/common/util/TestReflectionUtils.java
@@ -27,6 +27,14 @@ import org.apache.hudi.storage.StoragePath;
import org.apache.hudi.storage.StoragePathFilter;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
import static org.apache.hudi.common.util.ReflectionUtils.getMethod;
import static org.apache.hudi.common.util.ReflectionUtils.isSubClass;
@@ -57,4 +65,40 @@ public class TestReflectionUtils {
"listDirectEntries", StoragePathFilter.class).isPresent());
assertFalse(getMethod(HoodieStorage.class,
"nonExistentMethod").isPresent());
}
+
+ @ParameterizedTest
+ @ValueSource(strings = {"jar:file:/unused.jar!/org/apache/hudi/common/util",
"file:/invalid path"})
+ void testGetTopLevelClassesInClasspathSkipsInvalidResources(String
invalidResource) {
+ ClassLoader original = Thread.currentThread().getContextClassLoader();
+ ClassLoader loader = new ClassLoader(original) {
+ @Override
+ public Enumeration<URL> getResources(String name) throws IOException {
+ return Collections.enumeration(Arrays.asList(new URL(invalidResource),
TestReflectionUtils.class.getResource("")));
+ }
+ };
+ try {
+ Thread.currentThread().setContextClassLoader(loader);
+
assertTrue(ReflectionUtils.getTopLevelClassesInClasspath(TestReflectionUtils.class)
+ .anyMatch(TestReflectionUtils.class.getName()::equals));
+ } finally {
+ Thread.currentThread().setContextClassLoader(original);
+ }
+ }
+
+ @Test
+ void testGetTopLevelClassesInClasspathHandlesIOException() {
+ ClassLoader original = Thread.currentThread().getContextClassLoader();
+ ClassLoader loader = new ClassLoader(original) {
+ @Override
+ public Enumeration<URL> getResources(String name) throws IOException {
+ throw new IOException("Simulated failure enumerating resources");
+ }
+ };
+ try {
+ Thread.currentThread().setContextClassLoader(loader);
+
assertFalse(ReflectionUtils.getTopLevelClassesInClasspath(TestReflectionUtils.class).findAny().isPresent());
+ } finally {
+ Thread.currentThread().setContextClassLoader(original);
+ }
+ }
}
diff --git
a/hudi-io/src/main/java/org/apache/hudi/common/util/ReflectionUtils.java
b/hudi-io/src/main/java/org/apache/hudi/common/util/ReflectionUtils.java
index 6a6bd436ab41..819394207ba2 100644
--- a/hudi-io/src/main/java/org/apache/hudi/common/util/ReflectionUtils.java
+++ b/hudi-io/src/main/java/org/apache/hudi/common/util/ReflectionUtils.java
@@ -30,7 +30,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Enumeration;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -132,26 +132,30 @@ public class ReflectionUtils {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String packageName = clazz.getPackage().getName();
String path = packageName.replace('.', '/');
- Enumeration<URL> resources = null;
try {
- resources = classLoader.getResources(path);
+ return Collections.list(classLoader.getResources(path)).stream()
+ .map(ReflectionUtils::toDirectory)
+ .filter(Objects::nonNull)
+ .flatMap(directory -> findClasses(directory, packageName).stream());
} catch (IOException e) {
log.error("Unable to fetch Resources in package {}", packageName, e);
+ return Stream.empty();
}
- List<File> directories = new ArrayList<>();
- while (Objects.requireNonNull(resources).hasMoreElements()) {
- URL resource = resources.nextElement();
- try {
- directories.add(new File(resource.toURI()));
- } catch (URISyntaxException e) {
- log.error("Unable to get URI for {}", resource, e);
- }
- }
- List<String> classes = new ArrayList<>();
- for (File directory : directories) {
- classes.addAll(findClasses(directory, packageName));
+ }
+
+ /**
+ * Converts a package resource {@link URL} to a {@link File} directory, or
{@code null} if the URI is malformed or does not represent a file.
+ *
+ * @param resource the package resource URL
+ * @return the corresponding directory, or {@code null} if conversion fails
+ */
+ private static File toDirectory(URL resource) {
+ try {
+ return new File(resource.toURI());
+ } catch (URISyntaxException | IllegalArgumentException e) {
+ log.error("Unable to get URI for {}", resource, e);
+ return null;
}
- return classes.stream();
}
/**