Skip to content

Commit 7ea619a

Browse files
committed
Merge branch '6.2.x'
2 parents 99e4815 + 3dc2237 commit 7ea619a

File tree

6 files changed

+208
-9
lines changed

6 files changed

+208
-9
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.aot.hint.support;
1818

19+
import java.time.Instant;
1920
import java.time.LocalDate;
2021
import java.util.Collections;
2122
import java.util.List;
@@ -34,22 +35,30 @@
3435
* {@code org.springframework.core.convert.support.ObjectToObjectConverter}.
3536
*
3637
* @author Sebastien Deleuze
38+
* @author Sam Brannen
3739
* @since 6.0
3840
*/
3941
class ObjectToObjectConverterRuntimeHints implements RuntimeHintsRegistrar {
4042

4143
@Override
4244
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
4345
ReflectionHints reflectionHints = hints.reflection();
46+
4447
TypeReference sqlDateTypeReference = TypeReference.of("java.sql.Date");
4548
reflectionHints.registerTypeIfPresent(classLoader, sqlDateTypeReference.getName(), hint -> hint
4649
.withMethod("toLocalDate", Collections.emptyList(), ExecutableMode.INVOKE)
4750
.onReachableType(sqlDateTypeReference)
4851
.withMethod("valueOf", List.of(TypeReference.of(LocalDate.class)), ExecutableMode.INVOKE)
4952
.onReachableType(sqlDateTypeReference));
5053

54+
TypeReference sqlTimestampTypeReference = TypeReference.of("java.sql.Timestamp");
55+
reflectionHints.registerTypeIfPresent(classLoader, sqlTimestampTypeReference.getName(), hint -> hint
56+
.withMethod("from", List.of(TypeReference.of(Instant.class)), ExecutableMode.INVOKE)
57+
.onReachableType(sqlTimestampTypeReference));
58+
5159
reflectionHints.registerTypeIfPresent(classLoader, "org.springframework.http.HttpMethod",
5260
builder -> builder.withMethod("valueOf", List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE));
61+
5362
reflectionHints.registerTypeIfPresent(classLoader, "java.net.URI", MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
5463
}
5564

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.convert.support;
18+
19+
import java.time.Instant;
20+
import java.util.Date;
21+
22+
import org.springframework.core.convert.converter.Converter;
23+
24+
/**
25+
* Convert a {@link java.util.Date} to a {@link java.time.Instant}.
26+
*
27+
* <p>This includes conversion support for {@link java.sql.Timestamp} and other
28+
* subtypes of {@code java.util.Date}. Note, however, that an attempt to convert
29+
* a {@link java.sql.Date} or {@link java.sql.Time} to a {@code java.time.Instant}
30+
* results in an {@link UnsupportedOperationException} since those types do not
31+
* have time or date components, respectively.
32+
*
33+
* @author Sam Brannen
34+
* @since 6.2.9
35+
* @see Date#toInstant()
36+
* @see InstantToDateConverter
37+
*/
38+
final class DateToInstantConverter implements Converter<Date, Instant> {
39+
40+
@Override
41+
public Instant convert(Date date) {
42+
return date.toInstant();
43+
}
44+
45+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ public static void addDefaultConverters(ConverterRegistry converterRegistry) {
9191
addCollectionConverters(converterRegistry);
9292

9393
converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
94+
converterRegistry.addConverter(new DateToInstantConverter());
95+
converterRegistry.addConverter(new InstantToDateConverter());
9496
converterRegistry.addConverter(new StringToTimeZoneConverter());
9597
converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
9698
converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.convert.support;
18+
19+
import java.time.Instant;
20+
import java.util.Date;
21+
22+
import org.springframework.core.convert.TypeDescriptor;
23+
import org.springframework.core.convert.converter.ConditionalConverter;
24+
import org.springframework.core.convert.converter.Converter;
25+
26+
/**
27+
* Convert a {@link java.time.Instant} to a {@link java.util.Date}.
28+
*
29+
* <p>This does not include conversion support for target types which are subtypes
30+
* of {@code java.util.Date}.
31+
*
32+
* @author Sam Brannen
33+
* @since 6.2.9
34+
* @see Date#from(Instant)
35+
* @see DateToInstantConverter
36+
*/
37+
final class InstantToDateConverter implements ConditionalConverter, Converter<Instant, Date> {
38+
39+
@Override
40+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
41+
return targetType.getType().equals(Date.class);
42+
}
43+
44+
@Override
45+
public Date convert(Instant instant) {
46+
return Date.from(instant);
47+
}
48+
49+
}

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,53 @@
1717
package org.springframework.aot.hint.support;
1818

1919
import java.net.URI;
20+
import java.time.Instant;
2021
import java.time.LocalDate;
2122

2223
import org.junit.jupiter.api.BeforeEach;
2324
import org.junit.jupiter.api.Test;
2425

2526
import org.springframework.aot.hint.RuntimeHints;
2627
import org.springframework.aot.hint.RuntimeHintsRegistrar;
27-
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
2828
import org.springframework.core.io.support.SpringFactoriesLoader;
2929
import org.springframework.util.ClassUtils;
3030

3131
import static org.assertj.core.api.Assertions.assertThat;
32+
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
3233

3334
/**
3435
* Tests for {@link ObjectToObjectConverterRuntimeHints}.
3536
*
3637
* @author Sebastien Deleuze
38+
* @author Sam Brannen
3739
*/
3840
class ObjectToObjectConverterRuntimeHintsTests {
3941

40-
private RuntimeHints hints;
42+
private final RuntimeHints hints = new RuntimeHints();
43+
4144

4245
@BeforeEach
4346
void setup() {
44-
this.hints = new RuntimeHints();
47+
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
4548
SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories")
46-
.load(RuntimeHintsRegistrar.class).forEach(registrar -> registrar
47-
.registerHints(this.hints, ClassUtils.getDefaultClassLoader()));
49+
.load(RuntimeHintsRegistrar.class)
50+
.forEach(registrar -> registrar.registerHints(this.hints, classLoader));
4851
}
4952

5053
@Test
5154
void javaSqlDateHasHints() throws NoSuchMethodException {
52-
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(java.sql.Date.class, "toLocalDate")).accepts(this.hints);
53-
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(java.sql.Date.class.getMethod("valueOf", LocalDate.class))).accepts(this.hints);
55+
assertThat(reflection().onMethodInvocation(java.sql.Date.class, "toLocalDate")).accepts(this.hints);
56+
assertThat(reflection().onMethodInvocation(java.sql.Date.class.getMethod("valueOf", LocalDate.class))).accepts(this.hints);
57+
}
58+
59+
@Test // gh-35156
60+
void javaSqlTimestampHasHints() throws NoSuchMethodException {
61+
assertThat(reflection().onMethodInvocation(java.sql.Timestamp.class.getMethod("from", Instant.class))).accepts(this.hints);
5462
}
5563

5664
@Test
57-
void uriHasHints() throws NoSuchMethodException {
58-
assertThat(RuntimeHintsPredicates.reflection().onType(URI.class)).accepts(this.hints);
65+
void uriHasHints() {
66+
assertThat(reflection().onType(URI.class)).accepts(this.hints);
5967
}
6068

6169
}

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

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222
import java.math.BigInteger;
2323
import java.nio.charset.Charset;
2424
import java.nio.charset.StandardCharsets;
25+
import java.sql.Time;
26+
import java.sql.Timestamp;
27+
import java.time.Instant;
2528
import java.time.ZoneId;
2629
import java.util.AbstractList;
2730
import java.util.ArrayList;
2831
import java.util.Collection;
2932
import java.util.Collections;
3033
import java.util.Currency;
34+
import java.util.Date;
3135
import java.util.EnumSet;
3236
import java.util.HashMap;
3337
import java.util.LinkedHashMap;
@@ -1066,6 +1070,88 @@ public void handleOptionalList(Optional<List<Integer>> value) {
10661070
}
10671071
}
10681072

1073+
@Test // gh-35175
1074+
void convertDateToInstant() {
1075+
TypeDescriptor dateDescriptor = TypeDescriptor.valueOf(Date.class);
1076+
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
1077+
Date date = new Date();
1078+
1079+
// Conversion performed by DateToInstantConverter.
1080+
assertThat(conversionService.convert(date, dateDescriptor, instantDescriptor))
1081+
.isEqualTo(date.toInstant());
1082+
}
1083+
1084+
@Test // gh-35175
1085+
void convertSqlDateToInstant() {
1086+
TypeDescriptor sqlDateDescriptor = TypeDescriptor.valueOf(java.sql.Date.class);
1087+
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
1088+
java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
1089+
1090+
// DateToInstantConverter blindly invokes toInstant() on any java.util.Date
1091+
// subtype, which results in an UnsupportedOperationException since
1092+
// java.sql.Date does not have a time component. However, even if
1093+
// DateToInstantConverter were not registered, ObjectToObjectConverter
1094+
// would still attempt to invoke toInstant() on a java.sql.Date by convention,
1095+
// which results in the same UnsupportedOperationException.
1096+
assertThatExceptionOfType(ConversionFailedException.class)
1097+
.isThrownBy(() -> conversionService.convert(sqlDate, sqlDateDescriptor, instantDescriptor))
1098+
.withCauseExactlyInstanceOf(UnsupportedOperationException.class);
1099+
}
1100+
1101+
@Test // gh-35175
1102+
void convertSqlTimeToInstant() {
1103+
TypeDescriptor timeDescriptor = TypeDescriptor.valueOf(Time.class);
1104+
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
1105+
Time time = new Time(System.currentTimeMillis());
1106+
1107+
// DateToInstantConverter blindly invokes toInstant() on any java.util.Date
1108+
// subtype, which results in an UnsupportedOperationException since
1109+
// java.sql.Date does not have a time component. However, even if
1110+
// DateToInstantConverter were not registered, ObjectToObjectConverter
1111+
// would still attempt to invoke toInstant() on a java.sql.Date by convention,
1112+
// which results in the same UnsupportedOperationException.
1113+
assertThatExceptionOfType(ConversionFailedException.class)
1114+
.isThrownBy(() -> conversionService.convert(time, timeDescriptor, instantDescriptor))
1115+
.withCauseExactlyInstanceOf(UnsupportedOperationException.class);
1116+
}
1117+
1118+
@Test // gh-35175
1119+
void convertSqlTimestampToInstant() {
1120+
TypeDescriptor timestampDescriptor = TypeDescriptor.valueOf(Timestamp.class);
1121+
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
1122+
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
1123+
1124+
// Conversion performed by DateToInstantConverter.
1125+
assertThat(conversionService.convert(timestamp, timestampDescriptor, instantDescriptor))
1126+
.isEqualTo(timestamp.toInstant());
1127+
}
1128+
1129+
@Test // gh-35175
1130+
void convertInstantToDate() {
1131+
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
1132+
TypeDescriptor dateDescriptor = TypeDescriptor.valueOf(Date.class);
1133+
Date date = new Date();
1134+
Instant instant = date.toInstant();
1135+
1136+
// Conversion performed by InstantToDateConverter.
1137+
assertThat(conversionService.convert(instant, instantDescriptor, dateDescriptor))
1138+
.isExactlyInstanceOf(Date.class)
1139+
.isEqualTo(date);
1140+
}
1141+
1142+
@Test
1143+
void convertInstantToSqlTimestamp() {
1144+
TypeDescriptor instantDescriptor = TypeDescriptor.valueOf(Instant.class);
1145+
TypeDescriptor timestampDescriptor = TypeDescriptor.valueOf(Timestamp.class);
1146+
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
1147+
Instant instant = timestamp.toInstant();
1148+
1149+
// Conversion performed by ObjectToObjectConverter.
1150+
assertThat(conversionService.convert(instant, instantDescriptor, timestampDescriptor))
1151+
.isExactlyInstanceOf(Timestamp.class)
1152+
.isEqualTo(timestamp);
1153+
}
1154+
10691155

10701156
// test fields and helpers
10711157

0 commit comments

Comments
 (0)