Skip to content

Commit

Permalink
Add support for Kotlin value classes in BeanUtils
Browse files Browse the repository at this point in the history
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 spring-projectsgh-28638
  • Loading branch information
sdeleuze committed Aug 9, 2023
1 parent dd76ed7 commit 88c2a25
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@
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;
Expand Down Expand Up @@ -835,13 +838,22 @@ private static class KotlinDelegate {
* @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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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?)

}

0 comments on commit 88c2a25

Please sign in to comment.