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();
   }
 
   /**

Reply via email to