Skip to content

Commit 88c2a25

Browse files
committed
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
1 parent dd76ed7 commit 88c2a25

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

Diff for: spring-beans/src/main/java/org/springframework/beans/BeanUtils.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@
3131
import java.util.Set;
3232

3333
import kotlin.jvm.JvmClassMappingKt;
34+
import kotlin.jvm.JvmInline;
35+
import kotlin.reflect.KClass;
3436
import kotlin.reflect.KFunction;
3537
import kotlin.reflect.KParameter;
38+
import kotlin.reflect.full.KAnnotatedElements;
3639
import kotlin.reflect.full.KClasses;
3740
import kotlin.reflect.jvm.KCallablesJvm;
3841
import kotlin.reflect.jvm.ReflectJvmMapping;
@@ -835,13 +838,22 @@ private static class KotlinDelegate {
835838
* @see <a href="https://kotlinlang.org/docs/reference/classes.html#constructors">
836839
* https://kotlinlang.org/docs/reference/classes.html#constructors</a>
837840
*/
841+
@SuppressWarnings("unchecked")
838842
@Nullable
839843
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
840844
try {
841-
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
845+
KClass<T> kClass = JvmClassMappingKt.getKotlinClass(clazz);
846+
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(kClass);
842847
if (primaryCtor == null) {
843848
return null;
844849
}
850+
if (kClass.isValue() && !KAnnotatedElements
851+
.findAnnotations(kClass, JvmClassMappingKt.getKotlinClass(JvmInline.class)).isEmpty()) {
852+
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
853+
Assert.state(constructors.length == 1,
854+
"Kotlin value classes annotated with @JvmInline are expected to have a single JVM constructor");
855+
return (Constructor<T>) constructors[0];
856+
}
845857
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryCtor);
846858
if (constructor == null) {
847859
throw new IllegalStateException(

Diff for: spring-beans/src/test/kotlin/org/springframework/beans/BeanUtilsKotlinTests.kt

+52
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,45 @@ class BeanUtilsKotlinTests {
9090
BeanUtils.instantiateClass(PrivateClass::class.java.getDeclaredConstructor())
9191
}
9292

93+
@Test
94+
fun `Instantiate value class`() {
95+
val constructor = BeanUtils.findPrimaryConstructor(ValueClass::class.java)!!
96+
assertThat(constructor).isNotNull()
97+
val value = "Hello value class!"
98+
val instance = BeanUtils.instantiateClass(constructor, value)
99+
assertThat(instance).isEqualTo(ValueClass(value))
100+
}
101+
102+
@Test
103+
fun `Instantiate value class with multiple constructors`() {
104+
val constructor = BeanUtils.findPrimaryConstructor(ValueClassWithMultipleConstructors::class.java)!!
105+
assertThat(constructor).isNotNull()
106+
val value = "Hello value class!"
107+
val instance = BeanUtils.instantiateClass(constructor, value)
108+
assertThat(instance).isEqualTo(ValueClassWithMultipleConstructors(value))
109+
}
110+
111+
@Test
112+
fun `Instantiate class with value class parameter`() {
113+
val constructor = BeanUtils.findPrimaryConstructor(OneConstructorWithValueClass::class.java)!!
114+
assertThat(constructor).isNotNull()
115+
val value = ValueClass("Hello value class!")
116+
val instance = BeanUtils.instantiateClass(constructor, value)
117+
assertThat(instance).isEqualTo(OneConstructorWithValueClass(value))
118+
}
119+
120+
@Test
121+
fun `Instantiate class with nullable value class parameter`() {
122+
val constructor = BeanUtils.findPrimaryConstructor(OneConstructorWithNullableValueClass::class.java)!!
123+
assertThat(constructor).isNotNull()
124+
val value = ValueClass("Hello value class!")
125+
var instance = BeanUtils.instantiateClass(constructor, value)
126+
assertThat(instance).isEqualTo(OneConstructorWithNullableValueClass(value))
127+
instance = BeanUtils.instantiateClass(constructor, null)
128+
assertThat(instance).isEqualTo(OneConstructorWithNullableValueClass(null))
129+
}
130+
131+
93132
class Foo(val param1: String, val param2: Int)
94133

95134
class Bar(val param1: String, val param2: Int = 12)
@@ -128,4 +167,17 @@ class BeanUtilsKotlinTests {
128167

129168
private class PrivateClass
130169

170+
@JvmInline
171+
value class ValueClass(private val value: String)
172+
173+
@JvmInline
174+
value class ValueClassWithMultipleConstructors(private val value: String) {
175+
constructor() : this("Fail")
176+
constructor(part1: String, part2: String) : this("Fail")
177+
}
178+
179+
data class OneConstructorWithValueClass(val value: ValueClass)
180+
181+
data class OneConstructorWithNullableValueClass(val value: ValueClass?)
182+
131183
}

0 commit comments

Comments
 (0)