Skip to content

Commit feb2d95

Browse files
committed
GH-1312 Fix type resolution in FunctionTypeUtils
This issue addresses complex generic hierarchies and is related to issue in spring-core - spring-projects/spring-framework#33887 Resolves #1312
1 parent 4d79e82 commit feb2d95

File tree

2 files changed

+107
-89
lines changed

2 files changed

+107
-89
lines changed

spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java

Lines changed: 78 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public static Class<?> getRawType(Type type) {
179179
*/
180180
return ObjectUtils.isEmpty(upperbounds) ? Object.class : getRawType(upperbounds[0]);
181181
}
182-
return ResolvableType.forType(type).getRawClass();
182+
return ResolvableType.forType(type).getRawClass() == null ? Object.class : ResolvableType.forType(type).getRawClass();
183183
}
184184

185185
/**
@@ -228,6 +228,28 @@ else if (Function.class.isAssignableFrom(pojoFunctionClass) || BiFunction.class.
228228
return CollectionUtils.isEmpty(methods) ? null : methods.get(0);
229229
}
230230

231+
public static Type discoverFunctionTypeFromType(Type functionalType) {
232+
Type typeToReturn = null;
233+
if (Function.class.isAssignableFrom(getRawType(functionalType))) {
234+
ResolvableType functionType = ResolvableType.forType(functionalType).as(Function.class);
235+
typeToReturn = GenericTypeResolver.resolveType(functionType.getType(), getRawType(functionalType));
236+
}
237+
else if (Consumer.class.isAssignableFrom(getRawType(functionalType))) {
238+
ResolvableType functionType = ResolvableType.forType(functionalType).as(Consumer.class);
239+
240+
ResolvableType t = ResolvableType.forClassWithGenerics(getRawType(functionalType), functionType.getGeneric(0));
241+
Type t2 = t.getType();
242+
//Type t = ResolvableType.
243+
244+
typeToReturn = GenericTypeResolver.resolveType(functionType.getType(), functionType.getRawClass());
245+
}
246+
else {
247+
ResolvableType functionType = ResolvableType.forType(functionalType).as(Supplier.class);
248+
typeToReturn = GenericTypeResolver.resolveType(functionType.getType(), getRawType(functionalType));
249+
}
250+
return typeToReturn;
251+
}
252+
231253
public static Type discoverFunctionTypeFromClass(Class<?> functionalClass) {
232254
if (KotlinDetector.isKotlinPresent()) {
233255
if (Function1.class.isAssignableFrom(functionalClass)) {
@@ -248,15 +270,15 @@ else if (Function0.class.isAssignableFrom(functionalClass)) {
248270
}
249271
}
250272
}
251-
ResolvableType functionType = ResolvableType.forClass(functionalClass).as(Function.class);
273+
ResolvableType functionType = ResolvableType.forType(functionalClass).as(Function.class);
252274
typeToReturn = GenericTypeResolver.resolveType(functionType.getType(), functionalClass);
253275
}
254276
else if (Consumer.class.isAssignableFrom(functionalClass)) {
255-
ResolvableType functionType = ResolvableType.forClass(functionalClass).as(Consumer.class);
277+
ResolvableType functionType = ResolvableType.forType(functionalClass).as(Consumer.class);
256278
typeToReturn = GenericTypeResolver.resolveType(functionType.getType(), functionalClass);
257279
}
258280
else if (Supplier.class.isAssignableFrom(functionalClass)) {
259-
ResolvableType functionType = ResolvableType.forClass(functionalClass).as(Supplier.class);
281+
ResolvableType functionType = ResolvableType.forType(functionalClass).as(Supplier.class);
260282
typeToReturn = GenericTypeResolver.resolveType(functionType.getType(), functionalClass);
261283
}
262284
return typeToReturn;
@@ -285,6 +307,7 @@ public static Type discoverFunctionTypeFromFunctionFactoryMethod(Class<?> clazz,
285307
*/
286308
public static Type discoverFunctionTypeFromFunctionFactoryMethod(Method method) {
287309
return method.getGenericReturnType();
310+
// return discoverFunctionTypeFromClass(method.getReturnType());
288311
}
289312

290313
/**
@@ -375,6 +398,49 @@ public static Type getComponentTypeOfOutputType(Type functionType) {
375398
return getImmediateGenericType(inputType, 0);
376399
}
377400

401+
/**
402+
* Will resolve @{@link ResolvableType} to {@link Type} preserving all the resolved generics.
403+
* @param typeWithGenerics - instance of {@link ResolvableType}.
404+
* @return - {@link Type} representation of the provided {@link ResolvableType}.
405+
*/
406+
public static Type resolveType(ResolvableType typeWithGenerics) {
407+
if (typeWithGenerics.hasResolvableGenerics()) {
408+
ResolvableType[] generics = typeWithGenerics.getGenerics();
409+
List<ResolvableType> resolvedGenerics = new ArrayList<>();
410+
for (int i = 0; i < generics.length; i++) {
411+
ResolvableType genericType = typeWithGenerics.getGenerics()[i];
412+
resolvedGenerics.add(ResolvableType.forType(resolveType(genericType)));
413+
}
414+
return ResolvableType.forClassWithGenerics(typeWithGenerics.getRawClass(),
415+
resolvedGenerics.toArray(new ResolvableType[0])).getType();
416+
}
417+
else {
418+
return typeWithGenerics.resolve();
419+
}
420+
}
421+
422+
public static Type getOutputType(Type functionType) {
423+
assertSupportedTypes(functionType);
424+
if (isConsumer(functionType)) {
425+
logger.debug("Consumer does not have output type, returning null as output type.");
426+
return null;
427+
}
428+
429+
if (KotlinDetector.isKotlinPresent() && Function1.class.isAssignableFrom(getRawType(functionType))) { // Kotlin
430+
return ResolvableType.forType(getImmediateGenericType(functionType, 1)).getType();
431+
}
432+
else {
433+
ResolvableType resolvableFunctionType = isSupplier(functionType)
434+
? ResolvableType.forType(functionType).as(Supplier.class)
435+
: ResolvableType.forType(functionType).as(Function.class);
436+
ResolvableType generics = isSupplier(functionType)
437+
? resolvableFunctionType.getGenerics()[0]
438+
: resolvableFunctionType.getGenerics()[1];
439+
Type outputType = FunctionTypeUtils.resolveType(generics);
440+
return outputType == null || outputType instanceof TypeVariable<?> ? Object.class : outputType;
441+
}
442+
}
443+
378444
/**
379445
* Returns input type of function type that represents Function or Consumer.
380446
* @param functionType the Type of Function or Consumer
@@ -387,32 +453,16 @@ public static Type getInputType(Type functionType) {
387453
return null;
388454
}
389455

390-
ResolvableType resolvableFunctionType = ResolvableType.forType(functionType);
391-
392-
ResolvableType resolvableInputType = resolvableFunctionType.as(resolvableFunctionType.getRawClass());
393-
394-
if (resolvableInputType.getType() instanceof ParameterizedType) {
395-
return resolvableInputType.getGeneric(0).getType();
456+
if (KotlinDetector.isKotlinPresent() && Function1.class.isAssignableFrom(getRawType(functionType))) { // Kotlin
457+
return ResolvableType.forType(getImmediateGenericType(functionType, 1)).getType();
396458
}
397459
else {
398-
// will try another way. See GH-1251
399-
if (FunctionTypeUtils.isFunction(functionType)) {
400-
resolvableInputType = resolvableFunctionType.as(Function.class);
401-
}
402-
else {
403-
if (KotlinDetector.isKotlinPresent() && Function1.class.isAssignableFrom(getRawType(functionType))) { // Kotlin
404-
return ResolvableType.forType(getImmediateGenericType(functionType, 1)).getType();
405-
}
406-
else {
407-
resolvableInputType = resolvableFunctionType.as(Consumer.class);
408-
}
409-
}
410-
if (resolvableInputType.getType() instanceof ParameterizedType) {
411-
return resolvableInputType.getGeneric(0).getType();
412-
}
413-
else {
414-
return Object.class;
415-
}
460+
ResolvableType resolvableFunctionType = isConsumer(functionType)
461+
? ResolvableType.forType(functionType).as(Consumer.class)
462+
: ResolvableType.forType(functionType).as(Function.class);
463+
ResolvableType generics = resolvableFunctionType.getGenerics()[0];
464+
Type inputType = FunctionTypeUtils.resolveType(generics);
465+
return inputType == null || inputType instanceof TypeVariable<?> ? Object.class : inputType;
416466
}
417467
}
418468

@@ -475,52 +525,6 @@ public static String discoverBeanDefinitionNameByQualifier(ListableBeanFactory b
475525
}
476526
return null;
477527
}
478-
479-
public static Type getOutputType(Type functionType) {
480-
assertSupportedTypes(functionType);
481-
if (isConsumer(functionType)) {
482-
logger.debug("Consumer does not have output type, returning null as output type.");
483-
return null;
484-
}
485-
486-
ResolvableType resolvableFunctionType = ResolvableType.forType(functionType);
487-
488-
ResolvableType resolvableOutputType;
489-
if (FunctionTypeUtils.isFunction(functionType)) {
490-
resolvableOutputType = resolvableFunctionType.as(Function.class);
491-
}
492-
else {
493-
if (KotlinDetector.isKotlinPresent() && Function1.class.isAssignableFrom(getRawType(functionType))) { // Kotlin
494-
return ResolvableType.forType(getImmediateGenericType(functionType, 1)).getType();
495-
}
496-
else {
497-
resolvableOutputType = resolvableFunctionType.as(Supplier.class);
498-
}
499-
}
500-
501-
Type outputType;
502-
if (functionType instanceof Class functionTypeClass) {
503-
if (FunctionTypeUtils.isFunction(functionType)) {
504-
ResolvableType genericClass1 = resolvableOutputType.getGeneric(1);
505-
outputType = genericClass1.getType();
506-
outputType = (outputType instanceof TypeVariable) ? Object.class : GenericTypeResolver.resolveType(outputType, functionTypeClass);
507-
}
508-
else {
509-
ResolvableType genericClass0 = resolvableOutputType.getGeneric(0);
510-
outputType = genericClass0.getType();
511-
outputType = (outputType instanceof TypeVariable) ? Object.class : GenericTypeResolver.resolveType(outputType, functionTypeClass);
512-
}
513-
}
514-
else if (functionType instanceof ParameterizedType) {
515-
Type genericType = isSupplier(functionType) ? resolvableOutputType.getGeneric(0).getType() : resolvableOutputType.getGeneric(1).getType();
516-
outputType = GenericTypeResolver.resolveType(genericType, getRawType(functionType));
517-
}
518-
else {
519-
outputType = resolvableOutputType.getType();
520-
}
521-
return outputType instanceof TypeVariable ? Object.class : outputType;
522-
}
523-
524528
public static Type getImmediateGenericType(Type type, int index) {
525529
if (type instanceof ParameterizedType) {
526530
return ((ParameterizedType) type).getActualTypeArguments()[index];

spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,20 @@ public void testIsTypeCollection() {
116116
assertThat(FunctionTypeUtils.isTypeCollection(new ParameterizedTypeReference<Flux<Message<List<String>>>>() { }.getType())).isFalse();
117117
}
118118

119-
// @Test
120-
// public void testNoNpeFromIsMessage() {
121-
// FunctionTypeUtilsTests<Date> testService = new FunctionTypeUtilsTests<>();
122-
//
123-
// Method methodUnderTest =
124-
// ReflectionUtils.findMethod(testService.getClass(), "notAMessageMethod", AtomicReference.class);
125-
// MethodParameter methodParameter = MethodParameter.forExecutable(methodUnderTest, 0);
126-
//
127-
// assertThat(FunctionTypeUtils.isMessage(methodParameter.getGenericParameterType())).isFalse();
128-
// }
119+
@Test
120+
public void testWithComplexGenericsHierarchy() throws Exception {
121+
Type functionType = FunctionTypeUtils.discoverFunctionTypeFromFunctionFactoryMethod(FunctionTypeUtilsTests.class, "methodWithGenerics");
122+
Type inputType = FunctionTypeUtils.getInputType(functionType);
123+
Class typeClass = FunctionTypeUtils.getRawType(inputType);
124+
assertThat(typeClass).isAssignableFrom(Message.class);
125+
ParameterizedType parameterizedInputType = (ParameterizedType) inputType;
126+
Type[] typeArguments = parameterizedInputType.getActualTypeArguments();
127+
typeClass = FunctionTypeUtils.getRawType(typeArguments[0]);
128+
assertThat(typeClass).isAssignableFrom(List.class);
129+
typeArguments = ((ParameterizedType) typeArguments[0]).getActualTypeArguments();
130+
typeClass = FunctionTypeUtils.getRawType(typeArguments[0]);
131+
assertThat(typeClass).isAssignableFrom(SomeDomainObject.class);
132+
}
129133

130134
//@Test
131135
public void testPrimitiveFunctionInputTypes() {
@@ -166,7 +170,6 @@ public void testPrimitiveFunctionInputTypes() {
166170
assertThat(FunctionTypeUtils.getRawType(FunctionTypeUtils.getInputType(type))).isAssignableFrom(ToDoubleFunction.class);
167171
}
168172

169-
170173
//@Test
171174
public void testPrimitiveFunctionOutputTypes() {
172175
Type type = FunctionTypeUtils.discoverFunctionTypeFromClass(IntConsumer.class);
@@ -207,10 +210,6 @@ public void testPrimitiveFunctionOutputTypes() {
207210
assertThat(FunctionTypeUtils.getRawType(FunctionTypeUtils.getOutputType(type))).isAssignableFrom(ToDoubleFunction.class);
208211
}
209212

210-
// void notAMessageMethod(AtomicReference<T> payload) {
211-
//
212-
// }
213-
214213
private static Function<String, Integer> function() {
215214
return null;
216215
}
@@ -264,6 +263,10 @@ private Type getReturnType(String methodName) throws Exception {
264263
return FunctionTypeUtilsTests.class.getDeclaredMethod(methodName).getGenericReturnType();
265264
}
266265

266+
public static GenericBatchMessageListConsumer<SomeDomainObject> methodWithGenerics() {
267+
return new GenericBatchMessageListConsumer<SomeDomainObject>();
268+
}
269+
267270
//============
268271

269272
private interface MessageFunction extends Function<Message<String>, Message<String>> {
@@ -324,4 +327,15 @@ public static class SampleData {
324327

325328
}
326329

330+
public static class SomeDomainObject {
331+
332+
}
333+
public static class GenericBatchMessageListConsumer<T> implements Consumer<Message<List<T>>> {
334+
335+
@Override
336+
public void accept(Message<List<T>> listMessage) {
337+
338+
}
339+
}
340+
327341
}

0 commit comments

Comments
 (0)