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
pull/3394/head
Mark Paluch 2 months ago
parent
commit
f80a432eda
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 {
masks.add(mask); 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() { public int[] getDefaulting() {

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

@ -20,6 +20,7 @@ import kotlin.reflect.KParameter;
import kotlin.reflect.jvm.ReflectJvmMapping; import kotlin.reflect.jvm.ReflectJvmMapping;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
@ -53,7 +54,6 @@ class KotlinInstantiationDelegate {
private final Map<KParameter, Integer> indexByKParameter; private final Map<KParameter, Integer> indexByKParameter;
private final List<Function<@Nullable Object, @Nullable Object>> wrappers = new ArrayList<>(); private final List<Function<@Nullable Object, @Nullable Object>> wrappers = new ArrayList<>();
private final Constructor<?> constructorToInvoke; private final Constructor<?> constructorToInvoke;
private final boolean hasDefaultConstructorMarker;
public KotlinInstantiationDelegate(PreferredConstructor<?, ?> preferredConstructor, public KotlinInstantiationDelegate(PreferredConstructor<?, ?> preferredConstructor,
Constructor<?> constructorToInvoke) { Constructor<?> constructorToInvoke) {
@ -74,7 +74,6 @@ class KotlinInstantiationDelegate {
} }
this.constructorToInvoke = constructorToInvoke; this.constructorToInvoke = constructorToInvoke;
this.hasDefaultConstructorMarker = hasDefaultConstructorMarker(constructorToInvoke.getParameters());
for (KParameter kParameter : kParameters) { for (KParameter kParameter : kParameters) {
@ -93,11 +92,7 @@ class KotlinInstantiationDelegate {
* @return number of constructor arguments. * @return number of constructor arguments.
*/ */
public int getRequiredParameterCount() { public int getRequiredParameterCount() {
return constructorToInvoke.getParameterCount();
return hasDefaultConstructorMarker ? constructorToInvoke.getParameterCount()
: (constructorToInvoke.getParameterCount()
+ KotlinDefaultMask.getMaskCount(constructorToInvoke.getParameterCount())
+ /* DefaultConstructorMarker */1);
} }
/** /**
@ -154,13 +149,14 @@ class KotlinInstantiationDelegate {
} }
/** /**
* Resolves a {@link PreferredConstructor} to a synthetic Kotlin constructor accepting the same user-space parameters * Resolves a {@link PreferredConstructor} to the constructor to be invoked. This can be a synthetic Kotlin
* suffixed by Kotlin-specifics required for defaulting and the {@code kotlin.jvm.internal.DefaultConstructorMarker}. * 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 * @since 2.0
* @author Mark Paluch * @author Mark Paluch
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Nullable @Nullable
public static PreferredConstructor<?, ?> resolveKotlinJvmConstructor( public static PreferredConstructor<?, ?> resolveKotlinJvmConstructor(
@ -184,11 +180,18 @@ class KotlinInstantiationDelegate {
Class<?> entityType = detectedConstructor.getDeclaringClass(); Class<?> entityType = detectedConstructor.getDeclaringClass();
Constructor<?> hit = null; Constructor<?> hit = null;
Constructor<?> privateFallback = null;
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(detectedConstructor); KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(detectedConstructor);
for (Constructor<?> candidate : entityType.getDeclaredConstructors()) { 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()) { if (!candidate.isSynthetic()) {
continue; continue;
} }
@ -228,6 +231,10 @@ class KotlinInstantiationDelegate {
} }
} }
if (hit == null) {
return privateFallback;
}
return hit; return hit;
} }

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

@ -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 @JvmInline
value class MyNullableValueClass(val id: String? = "id") value class MyNullableValueClass(val id: String? = "id")

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

@ -199,6 +199,15 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
assertThat(instance.id.id).isEqualTo("hello") 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 @Test // GH-3389
fun `should use private default constructor for types using value class with default value`() { fun `should use private default constructor for types using value class with default value`() {

Loading…
Cancel
Save