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
|
* 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,
|
* public constructor with arguments, a single non-public constructor with arguments
|
||||||
* or simply a default constructor. Callers have to be prepared to resolve arguments
|
* or simply a default constructor.
|
||||||
* for the returned constructor's parameters, if any.
|
* <p>Callers have to be prepared to resolve arguments for the returned constructor's
|
||||||
|
* parameters, if any.
|
||||||
* @param clazz the class to check
|
* @param clazz the class to check
|
||||||
* @throws IllegalStateException in case of no unique constructor found at all
|
* @throws IllegalStateException in case of no unique constructor found at all
|
||||||
* @since 5.3
|
* @since 5.3
|
||||||
|
|
@ -253,19 +254,6 @@ public abstract class BeanUtils {
|
||||||
return (Constructor<T>) ctors[0];
|
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
|
// Several constructors -> let's try to take the default constructor
|
||||||
try {
|
try {
|
||||||
|
|
@ -282,11 +270,12 @@ public abstract class BeanUtils {
|
||||||
/**
|
/**
|
||||||
* Return the primary constructor of the provided class. For Kotlin classes, this
|
* Return the primary constructor of the provided class. For Kotlin classes, this
|
||||||
* returns the Java constructor corresponding to the Kotlin primary constructor
|
* returns the Java constructor corresponding to the Kotlin primary constructor
|
||||||
* (as defined in the Kotlin specification). Otherwise, in particular for non-Kotlin
|
* (as defined in the Kotlin specification). For Java records, this returns the
|
||||||
* classes, this simply returns {@code null}.
|
* canonical constructor. Otherwise, this simply returns {@code null}.
|
||||||
* @param clazz the class to check
|
* @param clazz the class to check
|
||||||
* @since 5.0
|
* @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
|
@Nullable
|
||||||
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
||||||
|
|
@ -294,6 +283,19 @@ public abstract class BeanUtils {
|
||||||
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
|
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
|
||||||
return KotlinDelegate.findPrimaryConstructor(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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -522,26 +522,36 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void resolveRecordConstructor() throws NoSuchMethodException {
|
void resolveMultipleRecordPublicConstructor() throws NoSuchMethodException {
|
||||||
assertThat(BeanUtils.getResolvableConstructor(RecordWithMultiplePublicConstructors.class))
|
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) {
|
private void assertSignatureEquals(Method desiredMethod, String signature) {
|
||||||
assertThat(BeanUtils.resolveSignature(signature, MethodSignatureBean.class)).isEqualTo(desiredMethod);
|
assertThat(BeanUtils.resolveSignature(signature, MethodSignatureBean.class)).isEqualTo(desiredMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public record RecordWithMultiplePublicConstructors(String value, String name) {
|
public record RecordWithMultiplePublicConstructors(String value, String name) {
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public RecordWithMultiplePublicConstructors(String value) {
|
public RecordWithMultiplePublicConstructors(String value) {
|
||||||
this(value, "default value");
|
this(value, "default value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Constructor<RecordWithMultiplePublicConstructors> getRecordWithMultipleVariationsConstructor() throws NoSuchMethodException {
|
record RecordWithMultiplePackagePrivateConstructors(String value, String name) {
|
||||||
return RecordWithMultiplePublicConstructors.class.getConstructor(String.class, String.class);
|
@SuppressWarnings("unused")
|
||||||
|
RecordWithMultiplePackagePrivateConstructors(String value) {
|
||||||
|
this(value, "default value");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class NumberHolder {
|
private static class NumberHolder {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue