diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index ad3b190f629c..e54cf0bf6a77 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -25,6 +25,7 @@ import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -396,7 +397,9 @@ public Class getParameterType() { if (paramType == null) { if (this.parameterIndex < 0) { Method method = getMethod(); - paramType = (method != null ? method.getReturnType() : void.class); + paramType = (method != null ? + (KotlinDetector.isKotlinType(getContainingClass()) ? + KotlinDelegate.getReturnType(method) : method.getReturnType()) : void.class); } else { paramType = this.executable.getParameterTypes()[this.parameterIndex]; @@ -416,7 +419,9 @@ public Type getGenericParameterType() { if (paramType == null) { if (this.parameterIndex < 0) { Method method = getMethod(); - paramType = (method != null ? method.getGenericReturnType() : void.class); + paramType = (method != null ? + (KotlinDetector.isKotlinType(getContainingClass()) ? + KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class); } else { Type[] genericParameterTypes = this.executable.getGenericParameterTypes(); @@ -777,6 +782,47 @@ else if (ctor != null) { } return false; } - } + /** + * Returns a return type of a method using Kotlin Reflection API. + * Introduced to support suspending functions. + */ + static private Class getReturnType(Method method) { + if (isSuspend(method)) { + final Class returnType = getSuspendReturnType(method).resolve(); + Assert.notNull(returnType, "returnType cannot be null"); + return returnType; + } else { + return method.getReturnType(); + } + } + + /** + * Returns a generic return type of a method using Kotlin Reflection API. + * Introduced to support suspending functions. + */ + static private Type getGenericReturnType(Method method) { + if (isSuspend(method)) { + return getSuspendReturnType(method).getType(); + } else { + return method.getGenericReturnType(); + } + } + + static private boolean isSuspend(Method method) { + KFunction function = ReflectJvmMapping.getKotlinFunction(method); + return (function != null && function.isSuspend()); + } + + static private ResolvableType getSuspendReturnType(Method method) { + ResolvableType cgeneric = ResolvableType.forMethodParameter(method, method.getParameterCount() - 1) + .getGeneric(0); + + if (cgeneric.getType() instanceof WildcardType) { + return ResolvableType.forType(((WildcardType) cgeneric.getType()).getLowerBounds()[0]); + } else { + return cgeneric; + } + } + } } diff --git a/spring-core/src/test/kotlin/org/springframework/core/KotlinGenericTypeResolverTests.kt b/spring-core/src/test/kotlin/org/springframework/core/KotlinGenericTypeResolverTests.kt new file mode 100644 index 000000000000..affaad869b0f --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/KotlinGenericTypeResolverTests.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core + +import org.junit.Assert.assertEquals +import org.junit.Test +import org.springframework.core.GenericTypeResolver.resolveReturnTypeArgument +import java.lang.reflect.Method + +/** + * Tests for Kotlin support in [MethodParameter.getGenericParameterType]. + * + * @author Konrad Kaminski + */ + +class KotlinGenericTypeResolverTests { + + @Test + fun methodReturnTypes() { + assertEquals(Integer::class.java, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods::class.java, "integer")!!, MyInterfaceType::class.java)) + assertEquals(String::class.java, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods::class.java, "string")!!, MyInterfaceType::class.java)) + assertEquals(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods::class.java, "raw")!!, MyInterfaceType::class.java)) + assertEquals(null, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods::class.java, "object")!!, MyInterfaceType::class.java)) + } + + private fun findMethod(clazz: Class<*>, name: String): Method? = + clazz.methods.firstOrNull { it.name == name } + + open class MyTypeWithMethods { + suspend fun integer(): MyInterfaceType? = null + + suspend fun string(): MySimpleInterfaceType? = null + + suspend fun `object`(): Any? = null + + suspend fun raw(): MyInterfaceType<*>? = null + } + + interface MyInterfaceType + + interface MySimpleInterfaceType: MyInterfaceType + + open class MySimpleTypeWithMethods: MyTypeWithMethods() +} \ No newline at end of file diff --git a/spring-core/src/test/kotlin/org/springframework/core/KotlinMethodParameterTests.kt b/spring-core/src/test/kotlin/org/springframework/core/KotlinMethodParameterTests.kt index 2157099a4ce8..dacf65471f33 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/KotlinMethodParameterTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/KotlinMethodParameterTests.kt @@ -16,12 +16,15 @@ package org.springframework.core -import java.lang.reflect.Method - +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test - -import org.junit.Assert.* +import java.lang.reflect.Method +import java.lang.reflect.TypeVariable +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.jvm.javaMethod /** * Tests for Kotlin support in [MethodParameter]. @@ -29,6 +32,7 @@ import org.junit.Assert.* * @author Raman Gupta * @author Sebastien Deleuze * @author Juergen Hoeller + * @author Konrad Kaminski */ class KotlinMethodParameterTests { @@ -57,6 +61,43 @@ class KotlinMethodParameterTests { assertFalse(MethodParameter(nonNullableMethod, -1).isOptional()) } + @Test + fun `Suspending function return type`() { + assertEquals(Number::class.java, returnParameterType("suspendFun")) + assertEquals(Number::class.java, returnGenericParameterType("suspendFun")) + + assertEquals(Producer::class.java, returnParameterType("suspendFun2")) + assertEquals("org.springframework.core.Producer", returnGenericParameterTypeName("suspendFun2")) + + assertEquals(Wrapper::class.java, returnParameterType("suspendFun3")) + assertEquals("org.springframework.core.Wrapper", returnGenericParameterTypeName("suspendFun3")) + + assertEquals(Consumer::class.java, returnParameterType("suspendFun4")) + assertEquals("org.springframework.core.Consumer", returnGenericParameterTypeName("suspendFun4")) + + assertEquals(Producer::class.java, returnParameterType("suspendFun5")) + assertTrue(returnGenericParameterType("suspendFun5") is TypeVariable<*>) + assertEquals("org.springframework.core.Producer", returnGenericParameterTypeBoundName("suspendFun5")) + + assertEquals(Wrapper::class.java, returnParameterType("suspendFun6")) + assertTrue(returnGenericParameterType("suspendFun6") is TypeVariable<*>) + assertEquals("org.springframework.core.Wrapper", returnGenericParameterTypeBoundName("suspendFun6")) + + assertEquals(Consumer::class.java, returnParameterType("suspendFun7")) + assertTrue(returnGenericParameterType("suspendFun7") is TypeVariable<*>) + assertEquals("org.springframework.core.Consumer", returnGenericParameterTypeBoundName("suspendFun7")) + + assertEquals(Object::class.java, returnParameterType("suspendFun8")) + assertEquals(Object::class.java, returnGenericParameterType("suspendFun8")) + } + + private fun returnParameterType(funName: String) = returnMethodParameter(funName).parameterType + private fun returnGenericParameterType(funName: String) = returnMethodParameter(funName).genericParameterType + private fun returnGenericParameterTypeName(funName: String) = returnGenericParameterType(funName).typeName + private fun returnGenericParameterTypeBoundName(funName: String) = (returnGenericParameterType(funName) as TypeVariable<*>).bounds[0].typeName + + private fun returnMethodParameter(funName: String) = + MethodParameter(this::class.declaredFunctions.first { it.name == funName }.javaMethod!!, -1) @Suppress("unused", "unused_parameter") fun nullable(p1: String?): Int? = 42 @@ -64,4 +105,33 @@ class KotlinMethodParameterTests { @Suppress("unused", "unused_parameter") fun nonNullable(p1: String): Int = 42 + @Suppress("unused", "unused_parameter") + suspend fun suspendFun(p1: String): Number = TODO() + + @Suppress("unused", "unused_parameter") + suspend fun suspendFun2(p1: String): Producer = TODO() + + @Suppress("unused", "unused_parameter") + suspend fun suspendFun3(p1: String): Wrapper = TODO() + + @Suppress("unused", "unused_parameter") + suspend fun suspendFun4(p1: String): Consumer = TODO() + + @Suppress("unused", "unused_parameter") + suspend fun > suspendFun5(p1: String): T = TODO() + + @Suppress("unused", "unused_parameter") + suspend fun > suspendFun6(p1: String): T = TODO() + + @Suppress("unused", "unused_parameter") + suspend fun > suspendFun7(p1: String): T = TODO() + + @Suppress("unused", "unused_parameter") + suspend fun suspendFun8(p1: String): Any? = TODO() } + +interface Producer + +interface Wrapper + +interface Consumer