Refine record canonical constructor support in BeanUtils
This commit refines the contribution with the following changes: - Move the support to findPrimaryConstructor - Use a for loop instead of a Stream for more efficiency - Support other visibilities than public - Polishing Closes gh-33707
This commit is contained in:
parent
514d6000d1
commit
effe606b28
|
@ -225,9 +225,10 @@ public abstract class BeanUtils {
|
|||
|
||||
/**
|
||||
* Return a resolvable constructor for the provided class, either a primary or single
|
||||
* public constructor with arguments, or a single non-public constructor with arguments,
|
||||
* or simply a default constructor. Callers have to be prepared to resolve arguments
|
||||
* for the returned constructor's parameters, if any.
|
||||
* public constructor with arguments, a single non-public constructor with arguments
|
||||
* or simply a default constructor.
|
||||
* <p>Callers have to be prepared to resolve arguments for the returned constructor's
|
||||
* parameters, if any.
|
||||
* @param clazz the class to check
|
||||
* @throws IllegalStateException in case of no unique constructor found at all
|
||||
* @since 5.3
|
||||
|
@ -253,19 +254,6 @@ public abstract class BeanUtils {
|
|||
return (Constructor<T>) ctors[0];
|
||||
}
|
||||
}
|
||||
else if (clazz.isRecord()) {
|
||||
try {
|
||||
// if record -> use canonical constructor, which is always presented
|
||||
Class<?>[] paramTypes
|
||||
= Arrays.stream(clazz.getRecordComponents())
|
||||
.map(RecordComponent::getType)
|
||||
.toArray(Class<?>[]::new);
|
||||
return clazz.getDeclaredConstructor(paramTypes);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
// Giving up with record...
|
||||
}
|
||||
}
|
||||
|
||||
// Several constructors -> let's try to take the default constructor
|
||||
try {
|
||||
|
@ -282,11 +270,12 @@ public abstract class BeanUtils {
|
|||
/**
|
||||
* Return the primary constructor of the provided class. For Kotlin classes, this
|
||||
* returns the Java constructor corresponding to the Kotlin primary constructor
|
||||
* (as defined in the Kotlin specification). Otherwise, in particular for non-Kotlin
|
||||
* classes, this simply returns {@code null}.
|
||||
* (as defined in the Kotlin specification). For Java records, this returns the
|
||||
* canonical constructor. Otherwise, this simply returns {@code null}.
|
||||
* @param clazz the class to check
|
||||
* @since 5.0
|
||||
* @see <a href="https://kotlinlang.org/docs/reference/classes.html#constructors">Kotlin docs</a>
|
||||
* @see <a href="https://kotlinlang.org/docs/reference/classes.html#constructors">Kotlin constructors</a>
|
||||
* @see <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.10.4">Record constructor declarations</a>
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
||||
|
@ -294,6 +283,19 @@ public abstract class BeanUtils {
|
|||
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
|
||||
return KotlinDelegate.findPrimaryConstructor(clazz);
|
||||
}
|
||||
if (clazz.isRecord()) {
|
||||
try {
|
||||
// Use the canonical constructor which is always present
|
||||
RecordComponent[] components = clazz.getRecordComponents();
|
||||
Class<?>[] paramTypes = new Class<?>[components.length];
|
||||
for (int i = 0; i < components.length; i++) {
|
||||
paramTypes[i] = components[i].getType();
|
||||
}
|
||||
return clazz.getDeclaredConstructor(paramTypes);
|
||||
}
|
||||
catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -522,26 +522,36 @@ class BeanUtilsTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void resolveRecordConstructor() throws NoSuchMethodException {
|
||||
void resolveMultipleRecordPublicConstructor() throws NoSuchMethodException {
|
||||
assertThat(BeanUtils.getResolvableConstructor(RecordWithMultiplePublicConstructors.class))
|
||||
.isEqualTo(getRecordWithMultipleVariationsConstructor());
|
||||
.isEqualTo(RecordWithMultiplePublicConstructors.class.getDeclaredConstructor(String.class, String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveMultipleRecordePackagePrivateConstructor() throws NoSuchMethodException {
|
||||
assertThat(BeanUtils.getResolvableConstructor(RecordWithMultiplePackagePrivateConstructors.class))
|
||||
.isEqualTo(RecordWithMultiplePackagePrivateConstructors.class.getDeclaredConstructor(String.class, String.class));
|
||||
}
|
||||
|
||||
private void assertSignatureEquals(Method desiredMethod, String signature) {
|
||||
assertThat(BeanUtils.resolveSignature(signature, MethodSignatureBean.class)).isEqualTo(desiredMethod);
|
||||
}
|
||||
|
||||
|
||||
public record RecordWithMultiplePublicConstructors(String value, String name) {
|
||||
@SuppressWarnings("unused")
|
||||
public RecordWithMultiplePublicConstructors(String value) {
|
||||
this(value, "default value");
|
||||
}
|
||||
}
|
||||
|
||||
private Constructor<RecordWithMultiplePublicConstructors> getRecordWithMultipleVariationsConstructor() throws NoSuchMethodException {
|
||||
return RecordWithMultiplePublicConstructors.class.getConstructor(String.class, String.class);
|
||||
record RecordWithMultiplePackagePrivateConstructors(String value, String name) {
|
||||
@SuppressWarnings("unused")
|
||||
RecordWithMultiplePackagePrivateConstructors(String value) {
|
||||
this(value, "default value");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class NumberHolder {
|
||||
|
||||
|
|
Loading…
Reference in New Issue