Add support for Kotlin value classes in BeanUtils
This commit adds support for Kotlin value classes annotated with @JvmInline to BeanUtils#findPrimaryConstructor. This is only the first step, more refinements are expected to be needed to achieve a comprehensive support of Kotlin values classes in Spring Framework. Closes gh-28638
This commit is contained in:
@@ -31,8 +31,11 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import kotlin.jvm.JvmClassMappingKt;
|
||||
import kotlin.jvm.JvmInline;
|
||||
import kotlin.reflect.KClass;
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.full.KAnnotatedElements;
|
||||
import kotlin.reflect.full.KClasses;
|
||||
import kotlin.reflect.jvm.KCallablesJvm;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
@@ -835,13 +838,22 @@ public abstract class BeanUtils {
|
||||
* @see <a href="https://kotlinlang.org/docs/reference/classes.html#constructors">
|
||||
* https://kotlinlang.org/docs/reference/classes.html#constructors</a>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
||||
try {
|
||||
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
|
||||
KClass<T> kClass = JvmClassMappingKt.getKotlinClass(clazz);
|
||||
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(kClass);
|
||||
if (primaryCtor == null) {
|
||||
return null;
|
||||
}
|
||||
if (kClass.isValue() && !KAnnotatedElements
|
||||
.findAnnotations(kClass, JvmClassMappingKt.getKotlinClass(JvmInline.class)).isEmpty()) {
|
||||
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
|
||||
Assert.state(constructors.length == 1,
|
||||
"Kotlin value classes annotated with @JvmInline are expected to have a single JVM constructor");
|
||||
return (Constructor<T>) constructors[0];
|
||||
}
|
||||
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryCtor);
|
||||
if (constructor == null) {
|
||||
throw new IllegalStateException(
|
||||
|
||||
@@ -90,6 +90,45 @@ class BeanUtilsKotlinTests {
|
||||
BeanUtils.instantiateClass(PrivateClass::class.java.getDeclaredConstructor())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Instantiate value class`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(ValueClass::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = "Hello value class!"
|
||||
val instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(ValueClass(value))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Instantiate value class with multiple constructors`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(ValueClassWithMultipleConstructors::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = "Hello value class!"
|
||||
val instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(ValueClassWithMultipleConstructors(value))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Instantiate class with value class parameter`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(OneConstructorWithValueClass::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = ValueClass("Hello value class!")
|
||||
val instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(OneConstructorWithValueClass(value))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Instantiate class with nullable value class parameter`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(OneConstructorWithNullableValueClass::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = ValueClass("Hello value class!")
|
||||
var instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(OneConstructorWithNullableValueClass(value))
|
||||
instance = BeanUtils.instantiateClass(constructor, null)
|
||||
assertThat(instance).isEqualTo(OneConstructorWithNullableValueClass(null))
|
||||
}
|
||||
|
||||
|
||||
class Foo(val param1: String, val param2: Int)
|
||||
|
||||
class Bar(val param1: String, val param2: Int = 12)
|
||||
@@ -128,4 +167,17 @@ class BeanUtilsKotlinTests {
|
||||
|
||||
private class PrivateClass
|
||||
|
||||
@JvmInline
|
||||
value class ValueClass(private val value: String)
|
||||
|
||||
@JvmInline
|
||||
value class ValueClassWithMultipleConstructors(private val value: String) {
|
||||
constructor() : this("Fail")
|
||||
constructor(part1: String, part2: String) : this("Fail")
|
||||
}
|
||||
|
||||
data class OneConstructorWithValueClass(val value: ValueClass)
|
||||
|
||||
data class OneConstructorWithNullableValueClass(val value: ValueClass?)
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user