diff --git a/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java b/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java index 13340fd87b6..0b8a0b5663a 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java +++ b/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java @@ -28,16 +28,13 @@ * Utility reflection methods focused on constructors, modeled after {@link MethodUtils}. * *

Known Limitations

- *

Accessing Public Constructors In A Default Access Superclass

+ *

Accessing Constructors In A Non-Public Class

*

- * There is an issue when invoking {@code public} constructors contained in a default access superclass. Reflection correctly locates these constructors and - * assigns them as {@code public}. However, an {@link IllegalAccessException} is thrown if the constructor is invoked. - *

- * - *

- * {@link ConstructorUtils} contains a workaround for this situation: it will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} - * on this constructor. If this call succeeds, then the method can be invoked as normal. This call will only succeed when the application has sufficient - * security privileges. If this call fails then a warning will be logged and the method may fail. + * Constructors in non-public classes (such as package-private classes or classes enclosed in non-public classes) are + * not accessible. Methods such as {@link #getAccessibleConstructor(Class, Class[])} and + * {@link #getMatchingAccessibleConstructor(Class, Class[])} return {@code null} when invoked on non-public classes. + * Consequently, invocation methods such as {@link #invokeConstructor(Class, Object...)} and + * {@link #invokeExactConstructor(Class, Object...)} throw a {@link NoSuchMethodException}. *

* * @since 2.5 @@ -64,6 +61,9 @@ public class ConstructorUtils { */ public static Constructor getAccessibleConstructor(final Class cls, final Class... parameterTypes) { Objects.requireNonNull(cls, "cls"); + if (!isAccessible(cls)) { + return null; + } try { return getAccessibleConstructor(cls.getConstructor(parameterTypes)); } catch (final NoSuchMethodException e) { @@ -114,10 +114,16 @@ public static Constructor getAccessibleConstructor(final Constructor c */ public static Constructor getMatchingAccessibleConstructor(final Class cls, final Class... parameterTypes) { Objects.requireNonNull(cls, "cls"); + if (!isAccessible(cls)) { + return null; + } // see if we can find the constructor directly // most of the time this works and it's much faster try { - return MemberUtils.setAccessibleWorkaround(cls.getConstructor(parameterTypes)); + final Constructor ctor = getAccessibleConstructor(cls.getConstructor(parameterTypes)); + if (ctor != null) { + return MemberUtils.setAccessibleWorkaround(ctor); + } } catch (final NoSuchMethodException ignored) { // ignore } diff --git a/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java index 9bb73ae9898..5bbe2c62762 100644 --- a/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java @@ -46,6 +46,8 @@ private static class BaseClass { } static class PrivateClass { + private final String value; + @SuppressWarnings("unused") public static class PublicInnerClass { public PublicInnerClass() { @@ -54,6 +56,19 @@ public PublicInnerClass() { @SuppressWarnings("unused") public PrivateClass() { + this("default"); + } + + public PrivateClass(final String value) { + this.value = value; + } + + public PrivateClass(final Number number) { + this.value = String.valueOf(number); + } + + public String getValue() { + return value; } } @@ -242,6 +257,29 @@ void testVarArgsUnboxing() throws Exception { assertArrayEquals(new String[] { "2", "3" }, testBean.varArgs); } + @Test + void testGetMatchingAccessibleConstructorOnNonPublicClass() { + assertNull(ConstructorUtils.getMatchingAccessibleConstructor(PrivateClass.class)); + assertNull(ConstructorUtils.getMatchingAccessibleConstructor(PrivateClass.class, String.class)); + assertNull(ConstructorUtils.getMatchingAccessibleConstructor(PrivateClass.class, Integer.class)); + assertNull(ConstructorUtils.getMatchingAccessibleConstructor(PrivateClass.PublicInnerClass.class)); + } + + @Test + void testInvokeConstructorOnNonPublicClass() { + assertThrows(NoSuchMethodException.class, () -> ConstructorUtils.invokeConstructor(PrivateClass.class)); + assertThrows(NoSuchMethodException.class, () -> ConstructorUtils.invokeConstructor(PrivateClass.class, "test")); + assertThrows(NoSuchMethodException.class, () -> ConstructorUtils.invokeConstructor(PrivateClass.class, Integer.valueOf(1))); + assertThrows(NoSuchMethodException.class, () -> ConstructorUtils.invokeConstructor(PrivateClass.PublicInnerClass.class)); + } + + @Test + void testInvokeExactConstructorOnNonPublicClass() { + assertThrows(NoSuchMethodException.class, () -> ConstructorUtils.invokeExactConstructor(PrivateClass.class)); + assertThrows(NoSuchMethodException.class, () -> ConstructorUtils.invokeExactConstructor(PrivateClass.class, "test")); + assertThrows(NoSuchMethodException.class, () -> ConstructorUtils.invokeExactConstructor(PrivateClass.PublicInnerClass.class)); + } + private String toString(final Class[] c) { return Arrays.asList(c).toString(); }