@@ -216,6 +216,22 @@ public static ClassLoader getDefaultClassLoader() {
216216 return getDefaultClassLoader (classLoader );
217217 }
218218
219+ /**
220+ * Returns the given {@link ClassLoader} if it is non-null, otherwise falls back to
221+ * the {@linkplain ClassLoader#getSystemClassLoader() system ClassLoader}. A {@code null}
222+ * classLoader typically indicates the bootstrap ClassLoader.
223+ *
224+ * <h3>Example Usage</h3>
225+ * <pre>{@code
226+ * ClassLoader cl = Thread.currentThread().getContextClassLoader();
227+ * ClassLoader effective = ClassLoaderUtils.getDefaultClassLoader(cl);
228+ * // If cl is null (bootstrap), effective will be the system ClassLoader
229+ * }</pre>
230+ *
231+ * @param classLoader the {@link ClassLoader} to use, may be {@code null}
232+ * @return the provided classLoader if non-null, or the system ClassLoader otherwise
233+ * @since 1.0.0
234+ */
219235 static ClassLoader getDefaultClassLoader (ClassLoader classLoader ) {
220236 // classLoader is null indicates the bootstrap ClassLoader
221237 return classLoader == null ? getSystemClassLoader () : classLoader ;
@@ -546,6 +562,29 @@ public static Class<?> loadClass(@Nullable ClassLoader classLoader, @Nullable St
546562 return doLoadClass (actualClassLoader , className );
547563 }
548564
565+ /**
566+ * Loads a {@link Class} with the specified name using the given {@link ClassLoader}.
567+ * Returns {@code null} for blank class names. If the class cannot be loaded, this method
568+ * attempts to resolve it as a primitive type before returning {@code null}.
569+ *
570+ * <h3>Example Usage</h3>
571+ * <pre>{@code
572+ * ClassLoader cl = Thread.currentThread().getContextClassLoader();
573+ * Class<?> klass = ClassLoaderUtils.doLoadClass(cl, "java.lang.String");
574+ * System.out.println(klass); // class java.lang.String
575+ *
576+ * Class<?> primitive = ClassLoaderUtils.doLoadClass(cl, "int");
577+ * System.out.println(primitive); // int
578+ *
579+ * Class<?> missing = ClassLoaderUtils.doLoadClass(cl, " ");
580+ * System.out.println(missing); // null
581+ * }</pre>
582+ *
583+ * @param classLoader the {@link ClassLoader} used to load the class
584+ * @param className the fully qualified name of the class to load, may be blank
585+ * @return the loaded {@link Class}, or {@code null} if the name is blank or the class cannot be found
586+ * @since 1.0.0
587+ */
549588 protected static Class <?> doLoadClass (ClassLoader classLoader , String className ) {
550589 if (isBlank (className )) {
551590 return null ;
@@ -1000,6 +1039,24 @@ public static Set<ClassLoader> getInheritableClassLoaders(@Nullable ClassLoader
10001039 return unmodifiableSet (doGetInheritableClassLoaders (actualClassLoader ));
10011040 }
10021041
1042+ /**
1043+ * Traverses the parent chain of the given {@link ClassLoader} and collects every
1044+ * ClassLoader in the hierarchy into a {@link Set}, starting from the provided
1045+ * classLoader up to (but not including) the bootstrap ClassLoader ({@code null} parent).
1046+ *
1047+ * <h3>Example Usage</h3>
1048+ * <pre>{@code
1049+ * ClassLoader appCl = ClassLoaderUtils.class.getClassLoader();
1050+ * Set<ClassLoader> hierarchy = ClassLoaderUtils.doGetInheritableClassLoaders(appCl);
1051+ * // hierarchy contains appCl and its parent (e.g. PlatformClassLoader)
1052+ * hierarchy.forEach(cl -> System.out.println(cl));
1053+ * }</pre>
1054+ *
1055+ * @param classLoader the starting {@link ClassLoader}; must not be {@code null}
1056+ * @return a non-null {@link Set} of ClassLoaders in the inheritance chain
1057+ * @throws NullPointerException if {@code classLoader} is {@code null}
1058+ * @since 1.0.0
1059+ */
10031060 @ Nonnull
10041061 static Set <ClassLoader > doGetInheritableClassLoaders (ClassLoader classLoader ) throws NullPointerException {
10051062 Set <ClassLoader > classLoadersSet = newLinkedHashSet ();
@@ -1752,6 +1809,25 @@ static ClassLoader getCallerClassLoader(int invocationFrame) {
17521809 return classLoader ;
17531810 }
17541811
1812+ /**
1813+ * Resolves the effective {@link ClassLoader} to use. Returns the provided classLoader
1814+ * if it is non-null; otherwise delegates to {@link #getDefaultClassLoader()} to obtain
1815+ * a suitable default.
1816+ *
1817+ * <h3>Example Usage</h3>
1818+ * <pre>{@code
1819+ * ClassLoader provided = new URLClassLoader(new URL[0]);
1820+ * ClassLoader result = ClassLoaderUtils.findClassLoader(provided);
1821+ * // result == provided
1822+ *
1823+ * ClassLoader fallback = ClassLoaderUtils.findClassLoader(null);
1824+ * // fallback is the thread-context or system ClassLoader
1825+ * }</pre>
1826+ *
1827+ * @param classLoader the {@link ClassLoader} to use, may be {@code null}
1828+ * @return the provided classLoader if non-null, or the default ClassLoader otherwise
1829+ * @since 1.0.0
1830+ */
17551831 @ Nullable
17561832 static ClassLoader findClassLoader (@ Nullable ClassLoader classLoader ) {
17571833 return classLoader == null ? getDefaultClassLoader () : classLoader ;
@@ -1775,11 +1851,50 @@ static Class<?> invokeFindLoadedClassMethod(ClassLoader classLoader, String clas
17751851 return loadedClass ;
17761852 }
17771853
1854+ /**
1855+ * Logs an error message when the reflective invocation of
1856+ * {@link ClassLoader#findLoadedClass(String)} fails. The log includes the class name,
1857+ * the ClassLoader instance, and the current JVM vendor and version for diagnostics.
1858+ *
1859+ * <h3>Example Usage</h3>
1860+ * <pre>{@code
1861+ * try {
1862+ * // attempt reflective findLoadedClass invocation
1863+ * } catch (Throwable e) {
1864+ * ClassLoaderUtils.logOnFindLoadedClassInvocationFailed(
1865+ * Thread.currentThread().getContextClassLoader(),
1866+ * "com.example.MyClass", e);
1867+ * }
1868+ * }</pre>
1869+ *
1870+ * @param classLoader the {@link ClassLoader} on which the invocation was attempted
1871+ * @param className the name of the class that was being looked up
1872+ * @param e the {@link Throwable} that caused the failure
1873+ * @since 1.0.0
1874+ */
17781875 static void logOnFindLoadedClassInvocationFailed (ClassLoader classLoader , String className , Throwable e ) {
17791876 logger .error ("The findLoadedClass(className : '{}' : String) method of java.lang.ClassLoader[{}] can't be invoked in the current JVM[vendor : {} , version : {}]" ,
17801877 className , classLoader , JAVA_VENDOR , JAVA_VERSION , e );
17811878 }
17821879
1880+ /**
1881+ * Builds a cache key by concatenating the class name with the hash code of the given
1882+ * {@link ClassLoader}. This key uniquely identifies a class within a specific ClassLoader
1883+ * for caching purposes.
1884+ *
1885+ * <h3>Example Usage</h3>
1886+ * <pre>{@code
1887+ * ClassLoader cl = Thread.currentThread().getContextClassLoader();
1888+ * String key = ClassLoaderUtils.buildCacheKey(cl, "com.example.MyClass");
1889+ * // key is something like "com.example.MyClass1234567890"
1890+ * System.out.println(key);
1891+ * }</pre>
1892+ *
1893+ * @param classLoader the {@link ClassLoader} whose hash code is appended to the key
1894+ * @param className the fully qualified class name
1895+ * @return a cache key string composed of the class name and ClassLoader hash code
1896+ * @since 1.0.0
1897+ */
17831898 static String buildCacheKey (ClassLoader classLoader , String className ) {
17841899 String cacheKey = className + classLoader .hashCode ();
17851900 return cacheKey ;
@@ -1829,6 +1944,21 @@ boolean supports(String name) {
18291944 return true ;
18301945 }
18311946
1947+ /**
1948+ * Returns the resource name as-is without any transformation, since
1949+ * the {@code DEFAULT} resource type applies no normalization.
1950+ *
1951+ * <h3>Example Usage</h3>
1952+ * <pre>{@code
1953+ * String name = "META-INF/services/com.example.MyService";
1954+ * String normalized = ResourceType.DEFAULT.normalize(name);
1955+ * // normalized is "META-INF/services/com.example.MyService"
1956+ * }</pre>
1957+ *
1958+ * @param name the resource name to normalize
1959+ * @return the same resource name, unchanged
1960+ * @since 1.0.0
1961+ */
18321962 @ Override
18331963 public String normalize (String name ) {
18341964 return name ;
@@ -1853,6 +1983,24 @@ boolean supports(String name) {
18531983 return endsWith (name , CLASS_EXTENSION );
18541984 }
18551985
1986+ /**
1987+ * Normalizes a class resource name by replacing package-separator dots with
1988+ * slashes and ensuring the name ends with the {@code .class} extension.
1989+ * Returns {@code null} if the input is {@code null}.
1990+ *
1991+ * <h3>Example Usage</h3>
1992+ * <pre>{@code
1993+ * String normalized = ResourceType.CLASS.normalize("com.example.MyClass.class");
1994+ * // normalized is "com/example/MyClass.class"
1995+ *
1996+ * String withoutExt = ResourceType.CLASS.normalize("com.example.MyClass");
1997+ * // withoutExt is "com/example/MyClass.class"
1998+ * }</pre>
1999+ *
2000+ * @param name the class resource name to normalize, may be {@code null}
2001+ * @return the normalized path-style class resource name, or {@code null} if {@code name} is {@code null}
2002+ * @since 1.0.0
2003+ */
18562004 @ Override
18572005 public String normalize (String name ) {
18582006 if (name == null ) {
@@ -1887,6 +2035,24 @@ boolean supports(String name) {
18872035 return !CLASS .supports (name ) && !contains (name , SLASH ) && !contains (name , BACK_SLASH );
18882036 }
18892037
2038+ /**
2039+ * Normalizes a package resource name by replacing dots with slashes and
2040+ * ensuring the result ends with a trailing slash. Returns {@code null}
2041+ * if the input is {@code null}.
2042+ *
2043+ * <h3>Example Usage</h3>
2044+ * <pre>{@code
2045+ * String normalized = ResourceType.PACKAGE.normalize("com.example.mypackage");
2046+ * // normalized is "com/example/mypackage/"
2047+ *
2048+ * String alreadySlashed = ResourceType.PACKAGE.normalize("com/example/mypackage/");
2049+ * // alreadySlashed is "com/example/mypackage/"
2050+ * }</pre>
2051+ *
2052+ * @param name the dot-separated package name to normalize, may be {@code null}
2053+ * @return the slash-separated package path ending with a slash, or {@code null} if {@code name} is {@code null}
2054+ * @since 1.0.0
2055+ */
18902056 @ Override
18912057 String normalize (String name ) {
18922058 if (name == null ) {
0 commit comments