This is an automated email from the ASF dual-hosted git repository. garydgregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
commit 8b126aa9c30c16ea05ddcc79d9851ae3d64fb4fe Author: Gary Gregory <[email protected]> AuthorDate: Sat Oct 17 15:15:07 2026 -0400 Fix resource paths for unnamed packages (#1824) --- src/changes/changes.xml | 1 + .../org/apache/commons/lang3/ClassPathUtils.java | 65 +++++++++++++++++----- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index ef813c0b5..69da672a2 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -64,6 +64,7 @@ The <action> type attribute can be add,update,fix,remove. <action type="fix" dev="ggregory" due-to="alhuda, Gary Gregory">Treat an unmatched quote as closed in ExtendedMessageFormat (#1819).</action> <action type="fix" dev="ggregory" due-to="gaurav kumar pandey, Gary Gregory">Prevent StringUtils padding from splitting surrogate pairs (#1813).</action> <action issue="LANG-441" type="fix" dev="ggregory" due-to="Sebb, Valentin Rocher, Adarsh Kumar Omrey, Gary Gregory">DateUtils Javadoc does not fully define round() behavior.</action> + <action issue="LANG-1838" type="fix" dev="ggregory" due-to="Aryan R. Ganjoo, Gary Gregory">Fix resource paths for unnamed packages (#1824).</action> <!-- ADD --> <!-- UPDATE --> <action type="update" dev="ggregory" due-to="Gary Gregory">Bump org.apache.commons:commons-parent from 105 to 106.</action> diff --git a/src/main/java/org/apache/commons/lang3/ClassPathUtils.java b/src/main/java/org/apache/commons/lang3/ClassPathUtils.java index 4f380e3ba..052b22739 100644 --- a/src/main/java/org/apache/commons/lang3/ClassPathUtils.java +++ b/src/main/java/org/apache/commons/lang3/ClassPathUtils.java @@ -30,6 +30,25 @@ //@Immutable public class ClassPathUtils { + /** + * Gets the package name of the context class. + * <p> + * Returns an empty string for an unnamed package, including when {@link Class#getPackage()} returns {@code null} on Java 8. + * </p> + * + * @param context The context class, must not be {@code null}. + * @return The package name, or an empty string for an unnamed package. + * @throws NullPointerException Thrown if {@code context} is null, represents an array, a primitive type or {@code void}, or represents a named class whose + * package is unavailable. + */ + private static String getPackageName(final Class<?> context) { + final Package contextPackage = context.getPackage(); + if (contextPackage == null && !context.isArray() && !context.isPrimitive() && context.getName().indexOf('.') < 0) { + return StringUtils.EMPTY; + } + return Objects.requireNonNull(contextPackage, "context").getName(); + } + /** * Converts a package name to a Java path ('/'). * @@ -54,6 +73,22 @@ public static String pathToPackage(final String path) { return Objects.requireNonNull(path, "path").replace('/', '.'); } + /** + * Qualifies a resource name with a prefix and separator. + * <p> + * Returns {@code resourceName} unchanged when the prefix is empty. + * </p> + * + * @param prefix The package name or path, must not be {@code null}. + * @param resourceName The resource name, validated as non-null by the caller. + * @param separator The separator between the prefix and resource name. + * @return The qualified resource name, or the unchanged resource name when the prefix is empty. + * @throws NullPointerException Thrown if {@code prefix} is null. + */ + private static String qualify(final String prefix, final String resourceName, final char separator) { + return prefix.isEmpty() ? resourceName : prefix + separator + resourceName; + } + /** * Returns the fully qualified name for the resource with name {@code resourceName} relative to the given context. * @@ -61,7 +96,9 @@ public static String pathToPackage(final String path) { * Note that this method does not check whether the resource actually exists. It only constructs the name. Null inputs are not allowed. * </p> * - * <p>If the context class is in an unnamed package, returns {@code resourceName} unchanged.</p> + * <p> + * If the context class is in an unnamed package, returns {@code resourceName} unchanged. + * </p> * * <pre> * ClassPathUtils.toFullyQualifiedName(StringUtils.class, "StringUtils.properties") = "org.apache.commons.lang3.StringUtils.properties" @@ -76,9 +113,7 @@ public static String pathToPackage(final String path) { public static String toFullyQualifiedName(final Class<?> context, final String resourceName) { Objects.requireNonNull(context, "context"); Objects.requireNonNull(resourceName, "resourceName"); - final Package contextPackage = context.getPackage(); - return contextPackage == null && !context.isArray() && !context.isPrimitive() && context.getName().indexOf('.') < 0 - ? resourceName : toFullyQualifiedName(Objects.requireNonNull(contextPackage, "context"), resourceName); + return qualify(getPackageName(context), resourceName, '.'); } /** @@ -88,7 +123,9 @@ public static String toFullyQualifiedName(final Class<?> context, final String r * Note that this method does not check whether the resource actually exists. It only constructs the name. Null inputs are not allowed. * </p> * - * <p>If the context is an unnamed package, returns {@code resourceName} unchanged.</p> + * <p> + * If the context is an unnamed package, returns {@code resourceName} unchanged. + * </p> * * <pre> * ClassPathUtils.toFullyQualifiedName(StringUtils.class.getPackage(), "StringUtils.properties") = "org.apache.commons.lang3.StringUtils.properties" @@ -102,8 +139,7 @@ public static String toFullyQualifiedName(final Class<?> context, final String r public static String toFullyQualifiedName(final Package context, final String resourceName) { Objects.requireNonNull(context, "context"); Objects.requireNonNull(resourceName, "resourceName"); - final String packageName = context.getName(); - return packageName.isEmpty() ? resourceName : packageName + "." + resourceName; + return qualify(context.getName(), resourceName, '.'); } /** @@ -113,7 +149,9 @@ public static String toFullyQualifiedName(final Package context, final String re * Note that this method does not check whether the resource actually exists. It only constructs the path. Null inputs are not allowed. * </p> * - * <p>If the context class is in an unnamed package, returns {@code resourceName} unchanged.</p> + * <p> + * If the context class is in an unnamed package, returns {@code resourceName} unchanged. + * </p> * * <pre> * ClassPathUtils.toFullyQualifiedPath(StringUtils.class, "StringUtils.properties") = "org/apache/commons/lang3/StringUtils.properties" @@ -128,9 +166,7 @@ public static String toFullyQualifiedName(final Package context, final String re public static String toFullyQualifiedPath(final Class<?> context, final String resourceName) { Objects.requireNonNull(context, "context"); Objects.requireNonNull(resourceName, "resourceName"); - final Package contextPackage = context.getPackage(); - return contextPackage == null && !context.isArray() && !context.isPrimitive() && context.getName().indexOf('.') < 0 - ? resourceName : toFullyQualifiedPath(Objects.requireNonNull(contextPackage, "context"), resourceName); + return qualify(packageToPath(getPackageName(context)), resourceName, '/'); } /** @@ -140,7 +176,9 @@ public static String toFullyQualifiedPath(final Class<?> context, final String r * Note that this method does not check whether the resource actually exists. It only constructs the path. Null inputs are not allowed. * </p> * - * <p>If the context is an unnamed package, returns {@code resourceName} unchanged.</p> + * <p> + * If the context is an unnamed package, returns {@code resourceName} unchanged. + * </p> * * <pre> * ClassPathUtils.toFullyQualifiedPath(StringUtils.class.getPackage(), "StringUtils.properties") = "org/apache/commons/lang3/StringUtils.properties" @@ -154,8 +192,7 @@ public static String toFullyQualifiedPath(final Class<?> context, final String r public static String toFullyQualifiedPath(final Package context, final String resourceName) { Objects.requireNonNull(context, "context"); Objects.requireNonNull(resourceName, "resourceName"); - final String packageName = context.getName(); - return packageName.isEmpty() ? resourceName : packageToPath(packageName) + "/" + resourceName; + return qualify(packageToPath(context.getName()), resourceName, '/'); } /**
