Browse Source

Fix nullness mismatch for Converter/ConverterFactory

In gh-35947, the `Converter` contract was refined to allow for nullable
return values. This created a mismatch with the `ConverterFactory`
contract.

This commit fixes this mismatch by allowing nullable return values in
`Converter` instances created by `ConverterFactory`.

Fixes gh-36063
pull/36069/head
Brian Clozel 1 month ago
parent
commit
012fb29097
  1. 4
      spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java
  2. 4
      spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java
  3. 4
      spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java
  4. 4
      spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java
  5. 2
      spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java
  6. 2
      spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java
  7. 53
      spring-core/src/test/kotlin/org/springframework/core/convert/converter/ConverterFactoryNullnessTests.kt

4
spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.core.convert.converter;
import org.jspecify.annotations.Nullable;
/**
* A factory for "ranged" converters that can convert objects from S to subtypes of R.
*
@ -36,6 +38,6 @@ public interface ConverterFactory<S, R> { @@ -36,6 +38,6 @@ public interface ConverterFactory<S, R> {
* @param targetType the target type to convert to
* @return a converter from S to T
*/
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
<T extends R> Converter<S, @Nullable T> getConverter(Class<T> targetType);
}

4
spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.core.convert.support;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.util.NumberUtils;
@ -41,7 +43,7 @@ import org.springframework.util.NumberUtils; @@ -41,7 +43,7 @@ import org.springframework.util.NumberUtils;
final class CharacterToNumberFactory implements ConverterFactory<Character, Number> {
@Override
public <T extends Number> Converter<Character, T> getConverter(Class<T> targetType) {
public <T extends Number> Converter<Character, @Nullable T> getConverter(Class<T> targetType) {
return new CharacterToNumber<>(targetType);
}

4
spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.core.convert.support;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
@ -30,7 +32,7 @@ import org.springframework.core.convert.converter.ConverterFactory; @@ -30,7 +32,7 @@ import org.springframework.core.convert.converter.ConverterFactory;
final class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
@Override
public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
public <T extends Enum> Converter<Integer, @Nullable T> getConverter(Class<T> targetType) {
return new IntegerToEnum(ConversionUtils.getEnumType(targetType));
}

4
spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.core.convert.support;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalConverter;
import org.springframework.core.convert.converter.Converter;
@ -43,7 +45,7 @@ import org.springframework.util.NumberUtils; @@ -43,7 +45,7 @@ import org.springframework.util.NumberUtils;
final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number>, ConditionalConverter {
@Override
public <T extends Number> Converter<Number, T> getConverter(Class<T> targetType) {
public <T extends Number> Converter<Number, @Nullable T> getConverter(Class<T> targetType) {
return new NumberToNumber<>(targetType);
}

2
spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java

@ -32,7 +32,7 @@ import org.springframework.core.convert.converter.ConverterFactory; @@ -32,7 +32,7 @@ import org.springframework.core.convert.converter.ConverterFactory;
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
public <T extends Enum> Converter<String, @Nullable T> getConverter(Class<T> targetType) {
return new StringToEnum(ConversionUtils.getEnumType(targetType));
}

2
spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java

@ -43,7 +43,7 @@ import org.springframework.util.NumberUtils; @@ -43,7 +43,7 @@ import org.springframework.util.NumberUtils;
final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {
@Override
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
public <T extends Number> Converter<String, @Nullable T> getConverter(Class<T> targetType) {
return new StringToNumber<>(targetType);
}

53
spring-core/src/test/kotlin/org/springframework/core/convert/converter/ConverterFactoryNullnessTests.kt

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
/*
* Copyright 2025-present 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.core.convert.converter
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.reflect.full.primaryConstructor
/**
* @author Brian Clozel
*/
class ConverterFactoryNullnessTests {
@Test
fun converterFactoryWithNullableTypes() {
val factory = StringToIdConverterFactory
val userIdConverter = factory.getConverter(UserId::class.java)
assertThat(userIdConverter.convert("42")).isEqualTo(UserId("42"))
}
object StringToIdConverterFactory : ConverterFactory<String, Id> {
override fun <T : Id> getConverter(targetType: Class<T>): Converter<String, T?> {
val constructor = checkNotNull(targetType.kotlin.primaryConstructor)
return Converter { source ->
constructor.call(source)
}
}
}
abstract class Id {
abstract val value: String
}
data class UserId(override val value: String) : Id()
data class ProductId(override val value: String) : Id()
}
Loading…
Cancel
Save