From 55c4fcf4f77e119a72568d45cb3cd0cf5ed47932 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 11 Apr 2025 12:26:00 +0200 Subject: [PATCH] GH-1260 Additional fix to Kotlin type resolution for generics This is specifically relevant to the way Kotlin represents types. For example List resolves to List which becomes the WildCard unlike in Java Resolves #1260 --- .../context/catalog/FunctionTypeUtils.java | 25 +++++++++--- .../kotlin/KotlinTypeDiscoveryTests.java | 40 +++++++++++++++++++ .../kotlin/KotlinComponentMessageFunction.kt | 15 +++++++ 3 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/KotlinTypeDiscoveryTests.java create mode 100644 spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/KotlinComponentMessageFunction.kt diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java index 451d8b10c..3fc05f078 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java @@ -68,6 +68,7 @@ import org.springframework.core.ResolvableType; import org.springframework.messaging.Message; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -166,10 +167,13 @@ public final class FunctionTypeUtils { * @return instance of {@link Class} as raw representation of the provided {@link Type} */ public static Class getRawType(Type type) { -// ((WildcardType) type).getUpperBounds(); -// Class cazz = ResolvableType.forType(type).getRawClass(); if (type instanceof WildcardType) { - return Object.class; + Type[] upperbounds = ((WildcardType) type).getUpperBounds(); + /* + * Kotlin may have something like this which is technically a whildcard yet it has upper/lower types. + * See GH-1260 + */ + return ObjectUtils.isEmpty(upperbounds) ? Object.class : getRawType(upperbounds[0]); } return ResolvableType.forType(type).getRawClass(); } @@ -392,7 +396,12 @@ public final class FunctionTypeUtils { resolvableInputType = resolvableFunctionType.as(Function.class); } else { - resolvableInputType = resolvableFunctionType.as(Consumer.class); + if (KotlinDetector.isKotlinPresent() && Function1.class.isAssignableFrom(getRawType(functionType))) { // Kotlin + return ResolvableType.forType(getImmediateGenericType(functionType, 1)).getType(); + } + else { + resolvableInputType = resolvableFunctionType.as(Consumer.class); + } } if (resolvableInputType.getType() instanceof ParameterizedType) { return resolvableInputType.getGeneric(0).getType(); @@ -477,7 +486,12 @@ public final class FunctionTypeUtils { resolvableOutputType = resolvableFunctionType.as(Function.class); } else { - resolvableOutputType = resolvableFunctionType.as(Supplier.class); + if (KotlinDetector.isKotlinPresent() && Function1.class.isAssignableFrom(getRawType(functionType))) { // Kotlin + return ResolvableType.forType(getImmediateGenericType(functionType, 1)).getType(); + } + else { + resolvableOutputType = resolvableFunctionType.as(Supplier.class); + } } Type outputType; @@ -629,6 +643,7 @@ public final class FunctionTypeUtils { Class candidateType = (Class) type; Assert.isTrue(Supplier.class.isAssignableFrom(candidateType) + || (KotlinDetector.isKotlinPresent() && (Function0.class.isAssignableFrom(candidateType) || Function1.class.isAssignableFrom(candidateType))) || Function.class.isAssignableFrom(candidateType) || Consumer.class.isAssignableFrom(candidateType) || FunctionRegistration.class.isAssignableFrom(candidateType) diff --git a/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/KotlinTypeDiscoveryTests.java b/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/KotlinTypeDiscoveryTests.java new file mode 100644 index 000000000..769c0b221 --- /dev/null +++ b/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/KotlinTypeDiscoveryTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019-2025 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 + * + * https://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.cloud.function.kotlin; + + + +import java.lang.reflect.Type; + +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class KotlinTypeDiscoveryTests { + + @Test + public void testOutputInputTypes() { + Type functionType = FunctionTypeUtils.discoverFunctionTypeFromClass(KotlinComponentMessageFunction.class); + Type outputType = FunctionTypeUtils.getOutputType(functionType); + assertThat(FunctionTypeUtils.isMessage(outputType)).isTrue(); + + Type inputType = FunctionTypeUtils.getInputType(functionType); + assertThat(FunctionTypeUtils.isMessage(inputType)).isTrue(); + } +} diff --git a/spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/KotlinComponentMessageFunction.kt b/spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/KotlinComponentMessageFunction.kt new file mode 100644 index 000000000..7f04cc57c --- /dev/null +++ b/spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/KotlinComponentMessageFunction.kt @@ -0,0 +1,15 @@ +package org.springframework.cloud.function.kotlin + +import org.springframework.messaging.Message +import org.springframework.messaging.MessageHeaders +import org.springframework.messaging.support.MessageBuilder +import org.springframework.stereotype.Component + +import java.util.function.Function + +@Component +class KotlinComponentMessageFunction : (List>) -> List> { + override fun invoke(input: List>): List> { + return input + } +}