diff --git a/pom.xml b/pom.xml
index 2473b93f59..af241907f1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-commons
- 2.1.0.BUILD-SNAPSHOT
+ 2.1.0.DATACMNS-1197-SNAPSHOT
Spring Data Core
diff --git a/src/main/java/org/springframework/data/util/ReflectionUtils.java b/src/main/java/org/springframework/data/util/ReflectionUtils.java
index 36103ef86d..430661857b 100644
--- a/src/main/java/org/springframework/data/util/ReflectionUtils.java
+++ b/src/main/java/org/springframework/data/util/ReflectionUtils.java
@@ -15,7 +15,12 @@
*/
package org.springframework.data.util;
+import kotlin.jvm.JvmClassMappingKt;
+import kotlin.reflect.KCallable;
+import kotlin.reflect.KClass;
import kotlin.reflect.KFunction;
+import kotlin.reflect.KMutableProperty;
+import kotlin.reflect.KProperty;
import kotlin.reflect.KType;
import kotlin.reflect.jvm.ReflectJvmMapping;
import kotlin.reflect.jvm.internal.impl.load.kotlin.header.KotlinClassHeader;
@@ -402,11 +407,37 @@ public static boolean isNullable(MethodParameter parameter) {
}
if (isSupportedKotlinClass(parameter.getDeclaringClass())) {
+ return KotlinReflectionUtils.isNullable(parameter);
+ }
+
+ return !parameter.getParameterType().isPrimitive();
+ }
+
+ /**
+ * Reflection utility methods specific to Kotlin reflection.
+ */
+ static class KotlinReflectionUtils {
+
+ /**
+ * Returns {@literal} whether the given {@link MethodParameter} is nullable. Its declaring method can reference a
+ * Kotlin function, property or interface property.
+ *
+ * @return {@literal true} if {@link MethodParameter} is nullable.
+ * @since 2.0.1
+ */
+ static boolean isNullable(MethodParameter parameter) {
- KFunction> kotlinFunction = ReflectJvmMapping.getKotlinFunction(parameter.getMethod());
+ Method method = parameter.getMethod();
+ KFunction> kotlinFunction = ReflectJvmMapping.getKotlinFunction(method);
if (kotlinFunction == null) {
- throw new IllegalArgumentException(String.format("Cannot resolve %s to a Kotlin function!", parameter));
+
+ // Fallback to own lookup because there's no public Kotlin API for that kind of lookup until
+ // https://youtrack.jetbrains.com/issue/KT-20768 gets resolved.
+ Optional extends KFunction> first = findKFunction(method);
+
+ kotlinFunction = first.orElseThrow(
+ () -> new IllegalArgumentException(String.format("Cannot resolve %s to a Kotlin function!", parameter)));
}
KType type = parameter.getParameterIndex() == -1 ? kotlinFunction.getReturnType()
@@ -415,6 +446,46 @@ public static boolean isNullable(MethodParameter parameter) {
return type.isMarkedNullable();
}
- return !parameter.getParameterType().isPrimitive();
+ /**
+ * Lookup a {@link Method} to a {@link KFunction}.
+ *
+ * @param method the JVM {@link Method} to look up.
+ * @return {@link Optional} wrapping a possibly existing {@link KFunction}.
+ */
+ private static Optional extends KFunction> findKFunction(Method method) {
+
+ KClass> kotlinClass = JvmClassMappingKt.getKotlinClass(method.getDeclaringClass());
+
+ return kotlinClass.getMembers() //
+ .stream() //
+ .flatMap(KotlinReflectionUtils::toKFunctionStream) //
+ .filter(it -> {
+
+ Method javaMethod = ReflectJvmMapping.getJavaMethod(it);
+ return javaMethod != null && javaMethod.equals(method);
+ }) //
+ .findFirst();
+ }
+
+ private static Stream extends KFunction> toKFunctionStream(KCallable> it) {
+
+ if (it instanceof KMutableProperty>) {
+
+ KMutableProperty property = (KMutableProperty>) it;
+ return Stream.of(property.getGetter(), property.getSetter());
+ }
+
+ if (it instanceof KProperty>) {
+
+ KProperty> property = (KProperty>) it;
+ return Stream.of(property.getGetter());
+ }
+
+ if (it instanceof KFunction>) {
+ return Stream.of((KFunction>) it);
+ }
+
+ return Stream.empty();
+ }
}
}
diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java
index dbc27013f6..60a7c7dd13 100755
--- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java
@@ -350,6 +350,14 @@ public void considersRequiredKotlinNullableParameter() {
assertThat(repository.findByOptionalId(null)).isNull();
}
+ @Test // DATACMNS-1197
+ public void considersNullabilityForKotlinInterfaceProperties() {
+
+ KotlinUserRepository repository = factory.getRepository(KotlinUserRepository.class);
+
+ assertThatThrownBy(repository::getFindRouteQuery).isInstanceOf(EmptyResultDataAccessException.class);
+ }
+
private ConvertingRepository prepareConvertingRepository(final Object expectedValue) {
when(factory.queryOne.execute(Mockito.any(Object[].class))).then(invocation -> {
diff --git a/src/test/kotlin/org/springframework/data/repository/core/support/KotlinUserRepository.kt b/src/test/kotlin/org/springframework/data/repository/core/support/KotlinUserRepository.kt
index d28c8dd6d8..27ef716ba6 100644
--- a/src/test/kotlin/org/springframework/data/repository/core/support/KotlinUserRepository.kt
+++ b/src/test/kotlin/org/springframework/data/repository/core/support/KotlinUserRepository.kt
@@ -28,4 +28,6 @@ interface KotlinUserRepository : Repository {
fun findById(username: String): User
fun findByOptionalId(username: String?): User?
+
+ val findRouteQuery: String
}
\ No newline at end of file