Skip to content

Commit 634d8cb

Browse files
mp911deodrotbohm
authored andcommitted
DATACMNS-1197 - Resolve Kotlin interface properties for nullability inspection.
We now resolve Kotlin interface properties to inspect these for nullability. Kotlin-reflect does not resolve interface property accessors yet so we need to handle this aspect ourselves. Related ticket: https://youtrack.jetbrains.com/issue/KT-20768. Original pull request: #254.
1 parent 8ce79c1 commit 634d8cb

File tree

3 files changed

+84
-3
lines changed

3 files changed

+84
-3
lines changed

src/main/java/org/springframework/data/util/ReflectionUtils.java

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
*/
1616
package org.springframework.data.util;
1717

18+
import kotlin.jvm.JvmClassMappingKt;
19+
import kotlin.reflect.KCallable;
20+
import kotlin.reflect.KClass;
1821
import kotlin.reflect.KFunction;
22+
import kotlin.reflect.KMutableProperty;
23+
import kotlin.reflect.KProperty;
1924
import kotlin.reflect.KType;
2025
import kotlin.reflect.jvm.ReflectJvmMapping;
2126
import kotlin.reflect.jvm.internal.impl.load.kotlin.header.KotlinClassHeader;
@@ -402,11 +407,37 @@ public static boolean isNullable(MethodParameter parameter) {
402407
}
403408

404409
if (isSupportedKotlinClass(parameter.getDeclaringClass())) {
410+
return KotlinReflectionUtils.isNullable(parameter);
411+
}
412+
413+
return !parameter.getParameterType().isPrimitive();
414+
}
415+
416+
/**
417+
* Reflection utility methods specific to Kotlin reflection.
418+
*/
419+
static class KotlinReflectionUtils {
420+
421+
/**
422+
* Returns {@literal} whether the given {@link MethodParameter} is nullable. Its declaring method can reference a
423+
* Kotlin function, property or interface property.
424+
*
425+
* @return {@literal true} if {@link MethodParameter} is nullable.
426+
* @since 2.0.1
427+
*/
428+
static boolean isNullable(MethodParameter parameter) {
405429

406-
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(parameter.getMethod());
430+
Method method = parameter.getMethod();
431+
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(method);
407432

408433
if (kotlinFunction == null) {
409-
throw new IllegalArgumentException(String.format("Cannot resolve %s to a Kotlin function!", parameter));
434+
435+
// Fallback to own lookup because there's no public Kotlin API for that kind of lookup until
436+
// https://youtrack.jetbrains.com/issue/KT-20768 gets resolved.
437+
Optional<? extends KFunction> first = findKFunction(method);
438+
439+
kotlinFunction = first.orElseThrow(
440+
() -> new IllegalArgumentException(String.format("Cannot resolve %s to a Kotlin function!", parameter)));
410441
}
411442

412443
KType type = parameter.getParameterIndex() == -1 ? kotlinFunction.getReturnType()
@@ -415,6 +446,46 @@ public static boolean isNullable(MethodParameter parameter) {
415446
return type.isMarkedNullable();
416447
}
417448

418-
return !parameter.getParameterType().isPrimitive();
449+
/**
450+
* Lookup a {@link Method} to a {@link KFunction}.
451+
*
452+
* @param method the JVM {@link Method} to look up.
453+
* @return {@link Optional} wrapping a possibly existing {@link KFunction}.
454+
*/
455+
private static Optional<? extends KFunction> findKFunction(Method method) {
456+
457+
KClass<?> kotlinClass = JvmClassMappingKt.getKotlinClass(method.getDeclaringClass());
458+
459+
return kotlinClass.getMembers() //
460+
.stream() //
461+
.flatMap(KotlinReflectionUtils::toKFunctionStream) //
462+
.filter(it -> {
463+
464+
Method javaMethod = ReflectJvmMapping.getJavaMethod(it);
465+
return javaMethod != null && javaMethod.equals(method);
466+
}) //
467+
.findFirst();
468+
}
469+
470+
private static Stream<? extends KFunction> toKFunctionStream(KCallable<?> it) {
471+
472+
if (it instanceof KMutableProperty<?>) {
473+
474+
KMutableProperty property = (KMutableProperty<?>) it;
475+
return Stream.of(property.getGetter(), property.getSetter());
476+
}
477+
478+
if (it instanceof KProperty<?>) {
479+
480+
KProperty<?> property = (KProperty<?>) it;
481+
return Stream.of(property.getGetter());
482+
}
483+
484+
if (it instanceof KFunction<?>) {
485+
return Stream.of((KFunction<?>) it);
486+
}
487+
488+
return Stream.empty();
489+
}
419490
}
420491
}

src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,14 @@ public void considersRequiredKotlinNullableParameter() {
350350
assertThat(repository.findByOptionalId(null)).isNull();
351351
}
352352

353+
@Test // DATACMNS-1197
354+
public void considersNullabilityForKotlinInterfaceProperties() {
355+
356+
KotlinUserRepository repository = factory.getRepository(KotlinUserRepository.class);
357+
358+
assertThatThrownBy(repository::getFindRouteQuery).isInstanceOf(EmptyResultDataAccessException.class);
359+
}
360+
353361
private ConvertingRepository prepareConvertingRepository(final Object expectedValue) {
354362

355363
when(factory.queryOne.execute(Mockito.any(Object[].class))).then(invocation -> {

src/test/kotlin/org/springframework/data/repository/core/support/KotlinUserRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@ interface KotlinUserRepository : Repository<User, String> {
2828
fun findById(username: String): User
2929

3030
fun findByOptionalId(username: String?): User?
31+
32+
val findRouteQuery: String
3133
}

0 commit comments

Comments
 (0)