Consider return type of static methods in ObjectToObjectConverter

Prior to this commit, ObjectToObjectConverter considered the return
type of non-static `to[targetType.simpleName]()` methods but did not
consider the return type of static `valueOf(sourceType)`,
`of(sourceType)`, and `from(sourceType)` methods.

This led to scenarios in which `canConvert()` returned `true`, but a
subsequent `convert()` invocation resulted in a
ConverterNotFoundException, which violates the contract of the
converter.

This commit addresses this issue by taking into account the return type
of a static valueOf/of/from factory method when determining if the
ObjectToObjectConverter supports a particular conversion. Whereas the
existing check in `determineToMethod()` ensures that the method return
type is assignable to the `targetType`, the new check in
`determineFactoryMethod()` leniently ensures that the method return
type and `targetType` are "related" (i.e., reside in the same type
hierarchy).

Closes gh-28609
This commit is contained in:
Sam Brannen
2022-06-13 17:06:18 +02:00
parent c278d8c656
commit 452f1b877c
3 changed files with 148 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 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.
@@ -41,15 +41,21 @@ import org.springframework.util.ReflectionUtils;
* <h3>Conversion Algorithm</h3>
* <ol>
* <li>Invoke a non-static {@code to[targetType.simpleName]()} method on the
* source object that has a return type equal to {@code targetType}, if such
* source object that has a return type assignable to {@code targetType}, if such
* a method exists. For example, {@code org.example.Bar Foo#toBar()} is a
* method that follows this convention.
* <li>Otherwise invoke a <em>static</em> {@code valueOf(sourceType)} or Java
* 8 style <em>static</em> {@code of(sourceType)} or {@code from(sourceType)}
* method on the {@code targetType}, if such a method exists.
* method on the {@code targetType} that has a return type <em>related</em> to
* {@code targetType}, if such a method exists. For example, a static
* {@code Foo.of(sourceType)} method that returns a {@code Foo},
* {@code SuperFooType}, or {@code SubFooType} is a method that follows this
* convention. {@link java.time.ZoneId#of(String)} is a concrete example of
* such a static factory method which returns a subtype of {@code ZoneId}.
* <li>Otherwise invoke a constructor on the {@code targetType} that accepts
* a single {@code sourceType} argument, if such a constructor exists.
* <li>Otherwise throw a {@link ConversionFailedException}.
* <li>Otherwise throw a {@link ConversionFailedException} or
* {@link IllegalStateException}.
* </ol>
*
* <p><strong>Warning</strong>: this converter does <em>not</em> support the
@@ -193,7 +199,18 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
method = ClassUtils.getStaticMethod(targetClass, "from", sourceClass);
}
}
return method;
return (method != null && areRelatedTypes(targetClass, method.getReturnType()) ? method : null);
}
/**
* Determine if the two types reside in the same type hierarchy (i.e., type 1
* is assignable to type 2 or vice versa).
* @since 5.3.21
* @see ClassUtils#isAssignable(Class, Class)
*/
private static boolean areRelatedTypes(Class<?> type1, Class<?> type2) {
return (ClassUtils.isAssignable(type1, type2) || ClassUtils.isAssignable(type2, type1));
}
@Nullable