Browse Source

Refine Kotlin constructor detection.

Attempt two-pass constructor detection in KotlinInstantiationDelegate to detect private constructors that are not synthetic ones.

See #3389
Original pull request: #3390
3.5.x
Mark Paluch 2 months ago
parent
commit
b935b293c9
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 7
      src/main/java/org/springframework/data/mapping/model/KotlinDefaultMask.java
  2. 29
      src/main/java/org/springframework/data/mapping/model/KotlinInstantiationDelegate.java
  3. 39
      src/test/kotlin/org/springframework/data/mapping/model/InlineClasses.kt
  4. 9
      src/test/kotlin/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiatorUnitTests.kt

7
src/main/java/org/springframework/data/mapping/model/KotlinDefaultMask.java

@ -148,7 +148,12 @@ public class KotlinDefaultMask { @@ -148,7 +148,12 @@ public class KotlinDefaultMask {
masks.add(mask);
}
return new KotlinDefaultMask(masks.stream().mapToInt(i -> i).toArray());
int[] defaulting = new int[masks.size()];
for (int i = 0; i < masks.size(); i++) {
defaulting[i] = masks.get(i);
}
return new KotlinDefaultMask(defaulting);
}
public int[] getDefaulting() {

29
src/main/java/org/springframework/data/mapping/model/KotlinInstantiationDelegate.java

@ -20,6 +20,7 @@ import kotlin.reflect.KParameter; @@ -20,6 +20,7 @@ import kotlin.reflect.KParameter;
import kotlin.reflect.jvm.ReflectJvmMapping;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
@ -52,7 +53,6 @@ class KotlinInstantiationDelegate { @@ -52,7 +53,6 @@ class KotlinInstantiationDelegate {
private final Map<KParameter, Integer> indexByKParameter;
private final List<Function<Object, Object>> wrappers = new ArrayList<>();
private final Constructor<?> constructorToInvoke;
private final boolean hasDefaultConstructorMarker;
public KotlinInstantiationDelegate(PreferredConstructor<?, ?> preferredConstructor,
Constructor<?> constructorToInvoke) {
@ -73,7 +73,6 @@ class KotlinInstantiationDelegate { @@ -73,7 +73,6 @@ class KotlinInstantiationDelegate {
}
this.constructorToInvoke = constructorToInvoke;
this.hasDefaultConstructorMarker = hasDefaultConstructorMarker(constructorToInvoke.getParameters());
for (KParameter kParameter : kParameters) {
@ -92,11 +91,7 @@ class KotlinInstantiationDelegate { @@ -92,11 +91,7 @@ class KotlinInstantiationDelegate {
* @return number of constructor arguments.
*/
public int getRequiredParameterCount() {
return hasDefaultConstructorMarker ? constructorToInvoke.getParameterCount()
: (constructorToInvoke.getParameterCount()
+ KotlinDefaultMask.getMaskCount(constructorToInvoke.getParameterCount())
+ /* DefaultConstructorMarker */1);
return constructorToInvoke.getParameterCount();
}
/**
@ -160,13 +155,14 @@ class KotlinInstantiationDelegate { @@ -160,13 +155,14 @@ class KotlinInstantiationDelegate {
}
/**
* Resolves a {@link PreferredConstructor} to a synthetic Kotlin constructor accepting the same user-space parameters
* suffixed by Kotlin-specifics required for defaulting and the {@code kotlin.jvm.internal.DefaultConstructorMarker}.
* Resolves a {@link PreferredConstructor} to the constructor to be invoked. This can be a synthetic Kotlin
* constructor accepting the same user-space parameters suffixed by Kotlin-specifics required for defaulting and the
* {@code kotlin.jvm.internal.DefaultConstructorMarker} or an actual non-synthetic constructor (i.e. private
* constructor).
*
* @since 2.0
* @author Mark Paluch
*/
@SuppressWarnings("unchecked")
@Nullable
public static PreferredConstructor<?, ?> resolveKotlinJvmConstructor(
@ -190,11 +186,18 @@ class KotlinInstantiationDelegate { @@ -190,11 +186,18 @@ class KotlinInstantiationDelegate {
Class<?> entityType = detectedConstructor.getDeclaringClass();
Constructor<?> hit = null;
Constructor<?> privateFallback = null;
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(detectedConstructor);
for (Constructor<?> candidate : entityType.getDeclaredConstructors()) {
// use only synthetic constructors
if (Modifier.isPrivate(candidate.getModifiers())) {
if (detectedConstructor.equals(candidate)) {
privateFallback = candidate;
}
}
// introspect only synthetic constructors
if (!candidate.isSynthetic()) {
continue;
}
@ -234,6 +237,10 @@ class KotlinInstantiationDelegate { @@ -234,6 +237,10 @@ class KotlinInstantiationDelegate {
}
}
if (hit == null) {
return privateFallback;
}
return hit;
}

39
src/test/kotlin/org/springframework/data/mapping/model/InlineClasses.kt

@ -47,9 +47,44 @@ data class WithMyValueClass(val id: MyValueClass) { @@ -47,9 +47,44 @@ data class WithMyValueClass(val id: MyValueClass) {
// ---------
}
data class WithMyValueClassPrivateConstructor private constructor(val id: MyValueClass)
data class WithMyValueClassPrivateConstructor private constructor(val id: MyValueClass) {
data class WithMyValueClassPrivateConstructorAndDefaultValue private constructor(val id: MyValueClass = MyValueClass("id"))
// ByteCode explanation
// ---------
// default constructor, detected by Discoverers.KOTLIN
// private WithMyValueClassPrivateConstructor(String id) {}
// ---------
}
data class WithNullableMyValueClassPrivateConstructor private constructor(val id: MyNullableValueClass?) {
// ByteCode explanation
// ---------
// default constructor, detected by Discoverers.KOTLIN
// private WithNullableMyValueClassPrivateConstructor(MyNullableValueClass id) {}
// ---------
}
data class WithMyValueClassPrivateConstructorAndDefaultValue private constructor(
val id: MyValueClass = MyValueClass(
"id"
)
) {
// ByteCode explanation
// ---------
// default constructor, detected by Discoverers.KOTLIN
// private WithMyValueClassPrivateConstructorAndDefaultValue(java.lang.String id) {}
// ---------
// ---------
// synthetic constructor that we actually want to use
// synthetic WithMyValueClassPrivateConstructorAndDefaultValue(java.lang.String id, int arg1, kotlin.jvm.internal.DefaultConstructorMarker arg2) {}
// ---------
}
@JvmInline
value class MyNullableValueClass(val id: String? = "id")

9
src/test/kotlin/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiatorUnitTests.kt

@ -202,6 +202,15 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests { @@ -202,6 +202,15 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
assertThat(instance.id.id).isEqualTo("hello")
}
@Test // GH-3389
fun `should use private default constructor for types using nullable value class`() {
every { provider.getParameterValue<String>(any()) } returns "hello"
val instance = construct(WithNullableMyValueClassPrivateConstructor::class)
assertThat(instance.id?.id).isEqualTo("hello")
}
@Test // GH-3389
fun `should use private default constructor for types using value class with default value`() {

Loading…
Cancel
Save