Browse Source

Merge branch '6.2.x'

pull/35186/head
Sam Brannen 5 months ago
parent
commit
7ea619aefb
  1. 9
      spring-core/src/main/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHints.java
  2. 45
      spring-core/src/main/java/org/springframework/core/convert/support/DateToInstantConverter.java
  3. 2
      spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
  4. 49
      spring-core/src/main/java/org/springframework/core/convert/support/InstantToDateConverter.java
  5. 26
      spring-core/src/test/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHintsTests.java
  6. 86
      spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java

9
spring-core/src/main/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHints.java

@ -16,6 +16,7 @@
package org.springframework.aot.hint.support; package org.springframework.aot.hint.support;
import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -34,6 +35,7 @@ import org.springframework.aot.hint.TypeReference;
* {@code org.springframework.core.convert.support.ObjectToObjectConverter}. * {@code org.springframework.core.convert.support.ObjectToObjectConverter}.
* *
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Sam Brannen
* @since 6.0 * @since 6.0
*/ */
class ObjectToObjectConverterRuntimeHints implements RuntimeHintsRegistrar { class ObjectToObjectConverterRuntimeHints implements RuntimeHintsRegistrar {
@ -41,6 +43,7 @@ class ObjectToObjectConverterRuntimeHints implements RuntimeHintsRegistrar {
@Override @Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
ReflectionHints reflectionHints = hints.reflection(); ReflectionHints reflectionHints = hints.reflection();
TypeReference sqlDateTypeReference = TypeReference.of("java.sql.Date"); TypeReference sqlDateTypeReference = TypeReference.of("java.sql.Date");
reflectionHints.registerTypeIfPresent(classLoader, sqlDateTypeReference.getName(), hint -> hint reflectionHints.registerTypeIfPresent(classLoader, sqlDateTypeReference.getName(), hint -> hint
.withMethod("toLocalDate", Collections.emptyList(), ExecutableMode.INVOKE) .withMethod("toLocalDate", Collections.emptyList(), ExecutableMode.INVOKE)
@ -48,8 +51,14 @@ class ObjectToObjectConverterRuntimeHints implements RuntimeHintsRegistrar {
.withMethod("valueOf", List.of(TypeReference.of(LocalDate.class)), ExecutableMode.INVOKE) .withMethod("valueOf", List.of(TypeReference.of(LocalDate.class)), ExecutableMode.INVOKE)
.onReachableType(sqlDateTypeReference)); .onReachableType(sqlDateTypeReference));
TypeReference sqlTimestampTypeReference = TypeReference.of("java.sql.Timestamp");
reflectionHints.registerTypeIfPresent(classLoader, sqlTimestampTypeReference.getName(), hint -> hint
.withMethod("from", List.of(TypeReference.of(Instant.class)), ExecutableMode.INVOKE)
.onReachableType(sqlTimestampTypeReference));
reflectionHints.registerTypeIfPresent(classLoader, "org.springframework.http.HttpMethod", reflectionHints.registerTypeIfPresent(classLoader, "org.springframework.http.HttpMethod",
builder -> builder.withMethod("valueOf", List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE)); builder -> builder.withMethod("valueOf", List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE));
reflectionHints.registerTypeIfPresent(classLoader, "java.net.URI", MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); reflectionHints.registerTypeIfPresent(classLoader, "java.net.URI", MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
} }

45
spring-core/src/main/java/org/springframework/core/convert/support/DateToInstantConverter.java

@ -0,0 +1,45 @@
/*
* Copyright 2002-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.support;
import java.time.Instant;
import java.util.Date;
import org.springframework.core.convert.converter.Converter;
/**
* Convert a {@link java.util.Date} to a {@link java.time.Instant}.
*
* <p>This includes conversion support for {@link java.sql.Timestamp} and other
* subtypes of {@code java.util.Date}. Note, however, that an attempt to convert
* a {@link java.sql.Date} or {@link java.sql.Time} to a {@code java.time.Instant}
* results in an {@link UnsupportedOperationException} since those types do not
* have time or date components, respectively.
*
* @author Sam Brannen
* @since 6.2.9
* @see Date#toInstant()
* @see InstantToDateConverter
*/
final class DateToInstantConverter implements Converter<Date, Instant> {
@Override
public Instant convert(Date date) {
return date.toInstant();
}
}

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

@ -91,6 +91,8 @@ public class DefaultConversionService extends GenericConversionService {
addCollectionConverters(converterRegistry); addCollectionConverters(converterRegistry);
converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry)); converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new DateToInstantConverter());
converterRegistry.addConverter(new InstantToDateConverter());
converterRegistry.addConverter(new StringToTimeZoneConverter()); converterRegistry.addConverter(new StringToTimeZoneConverter());
converterRegistry.addConverter(new ZoneIdToTimeZoneConverter()); converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter()); converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());

49
spring-core/src/main/java/org/springframework/core/convert/support/InstantToDateConverter.java

@ -0,0 +1,49 @@
/*
* Copyright 2002-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.support;
import java.time.Instant;
import java.util.Date;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalConverter;
import org.springframework.core.convert.converter.Converter;
/**
* Convert a {@link java.time.Instant} to a {@link java.util.Date}.
*
* <p>This does not include conversion support for target types which are subtypes
* of {@code java.util.Date}.
*
* @author Sam Brannen
* @since 6.2.9
* @see Date#from(Instant)
* @see DateToInstantConverter
*/
final class InstantToDateConverter implements ConditionalConverter, Converter<Instant, Date> {
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return targetType.getType().equals(Date.class);
}
@Override
public Date convert(Instant instant) {
return Date.from(instant);
}
}

26
spring-core/src/test/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHintsTests.java

@ -17,6 +17,7 @@
package org.springframework.aot.hint.support; package org.springframework.aot.hint.support;
import java.net.URI; import java.net.URI;
import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -24,38 +25,45 @@ import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
/** /**
* Tests for {@link ObjectToObjectConverterRuntimeHints}. * Tests for {@link ObjectToObjectConverterRuntimeHints}.
* *
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Sam Brannen
*/ */
class ObjectToObjectConverterRuntimeHintsTests { class ObjectToObjectConverterRuntimeHintsTests {
private RuntimeHints hints; private final RuntimeHints hints = new RuntimeHints();
@BeforeEach @BeforeEach
void setup() { void setup() {
this.hints = new RuntimeHints(); ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories")
.load(RuntimeHintsRegistrar.class).forEach(registrar -> registrar .load(RuntimeHintsRegistrar.class)
.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); .forEach(registrar -> registrar.registerHints(this.hints, classLoader));
} }
@Test @Test
void javaSqlDateHasHints() throws NoSuchMethodException { void javaSqlDateHasHints() throws NoSuchMethodException {
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(java.sql.Date.class, "toLocalDate")).accepts(this.hints); assertThat(reflection().onMethodInvocation(java.sql.Date.class, "toLocalDate")).accepts(this.hints);
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(java.sql.Date.class.getMethod("valueOf", LocalDate.class))).accepts(this.hints); assertThat(reflection().onMethodInvocation(java.sql.Date.class.getMethod("valueOf", LocalDate.class))).accepts(this.hints);
}
@Test // gh-35156
void javaSqlTimestampHasHints() throws NoSuchMethodException {
assertThat(reflection().onMethodInvocation(java.sql.Timestamp.class.getMethod("from", Instant.class))).accepts(this.hints);
} }
@Test @Test
void uriHasHints() throws NoSuchMethodException { void uriHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(URI.class)).accepts(this.hints); assertThat(reflection().onType(URI.class)).accepts(this.hints);
} }
} }

86
spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java

@ -22,12 +22,16 @@ import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Currency; import java.util.Currency;
import java.util.Date;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -1066,6 +1070,88 @@ class DefaultConversionServiceTests {
} }
} }
@Test // gh-35175
void convertDateToInstant() {
TypeDescriptor dateDescriptor = TypeDescriptor.valueOf(Date.class);
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
Date date = new Date();
// Conversion performed by DateToInstantConverter.
assertThat(conversionService.convert(date, dateDescriptor, instantDescriptor))
.isEqualTo(date.toInstant());
}
@Test // gh-35175
void convertSqlDateToInstant() {
TypeDescriptor sqlDateDescriptor = TypeDescriptor.valueOf(java.sql.Date.class);
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
// DateToInstantConverter blindly invokes toInstant() on any java.util.Date
// subtype, which results in an UnsupportedOperationException since
// java.sql.Date does not have a time component. However, even if
// DateToInstantConverter were not registered, ObjectToObjectConverter
// would still attempt to invoke toInstant() on a java.sql.Date by convention,
// which results in the same UnsupportedOperationException.
assertThatExceptionOfType(ConversionFailedException.class)
.isThrownBy(() -> conversionService.convert(sqlDate, sqlDateDescriptor, instantDescriptor))
.withCauseExactlyInstanceOf(UnsupportedOperationException.class);
}
@Test // gh-35175
void convertSqlTimeToInstant() {
TypeDescriptor timeDescriptor = TypeDescriptor.valueOf(Time.class);
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
Time time = new Time(System.currentTimeMillis());
// DateToInstantConverter blindly invokes toInstant() on any java.util.Date
// subtype, which results in an UnsupportedOperationException since
// java.sql.Date does not have a time component. However, even if
// DateToInstantConverter were not registered, ObjectToObjectConverter
// would still attempt to invoke toInstant() on a java.sql.Date by convention,
// which results in the same UnsupportedOperationException.
assertThatExceptionOfType(ConversionFailedException.class)
.isThrownBy(() -> conversionService.convert(time, timeDescriptor, instantDescriptor))
.withCauseExactlyInstanceOf(UnsupportedOperationException.class);
}
@Test // gh-35175
void convertSqlTimestampToInstant() {
TypeDescriptor timestampDescriptor = TypeDescriptor.valueOf(Timestamp.class);
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
// Conversion performed by DateToInstantConverter.
assertThat(conversionService.convert(timestamp, timestampDescriptor, instantDescriptor))
.isEqualTo(timestamp.toInstant());
}
@Test // gh-35175
void convertInstantToDate() {
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
TypeDescriptor dateDescriptor = TypeDescriptor.valueOf(Date.class);
Date date = new Date();
Instant instant = date.toInstant();
// Conversion performed by InstantToDateConverter.
assertThat(conversionService.convert(instant, instantDescriptor, dateDescriptor))
.isExactlyInstanceOf(Date.class)
.isEqualTo(date);
}
@Test
void convertInstantToSqlTimestamp() {
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
TypeDescriptor timestampDescriptor = TypeDescriptor.valueOf(Timestamp.class);
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
Instant instant = timestamp.toInstant();
// Conversion performed by ObjectToObjectConverter.
assertThat(conversionService.convert(instant, instantDescriptor, timestampDescriptor))
.isExactlyInstanceOf(Timestamp.class)
.isEqualTo(timestamp);
}
// test fields and helpers // test fields and helpers

Loading…
Cancel
Save