diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java
index d0591716924..fbba3a3b2c1 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java
@@ -160,14 +160,12 @@ final class SynthesizedMergedAnnotationInvocationHandler i
}
/**
- * This method currently does not address the following issues which we may
+ * This method currently does not address the following issue which we may
* choose to address at a later point in time.
*
*
* - non-ASCII, non-visible, and non-printable characters within a character
* or String literal are not escaped.
- * - formatting for float and double values does not take into account whether
- * a value is not a number (NaN) or infinite.
*
* @param value the attribute value to format
* @return the formatted string representation
@@ -199,10 +197,30 @@ final class SynthesizedMergedAnnotationInvocationHandler i
return Long.toString((Long) value) + 'L';
}
if (type == Float.class) {
- return Float.toString((Float) value) + 'f';
+ float floatValue = (Float) value;
+ if (Float.isNaN(floatValue)) {
+ return "0.0f/0.0f";
+ }
+ if (floatValue == Float.POSITIVE_INFINITY) {
+ return "1.0f/0.0f";
+ }
+ if (floatValue == Float.NEGATIVE_INFINITY) {
+ return "-1.0f/0.0f";
+ }
+ return Float.toString(floatValue) + 'f';
}
if (type == Double.class) {
- return Double.toString((Double) value);
+ double doubleValue = (Double) value;
+ if (Double.isNaN(doubleValue)) {
+ return "0.0/0.0";
+ }
+ if (doubleValue == Double.POSITIVE_INFINITY) {
+ return "1.0/0.0";
+ }
+ if (doubleValue == Double.NEGATIVE_INFINITY) {
+ return "-1.0/0.0";
+ }
+ return Double.toString(doubleValue);
}
if (value instanceof Enum> e) {
return e.name();
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java
index 4aa1adff507..29e85299707 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java
@@ -2087,6 +2087,30 @@ class MergedAnnotationsTests {
.isEqualTo("@%s({\"FromValueAttributeMeta\"})", ValueAttribute.class.getCanonicalName());
}
+ @Test // gh-37244
+ void toStringForSynthesizedAnnotationsWithNonFiniteFloatingPointValues() {
+ Map attributes = Map.of(
+ "name", "test",
+ "floatValue", Float.NaN,
+ "doubleValue", Double.NaN);
+ RequestMapping mapping = MergedAnnotation.of(RequestMapping.class, attributes).synthesize();
+ assertThat(mapping).asString().contains("floatValue=0.0f/0.0f", "doubleValue=0.0/0.0");
+
+ attributes = Map.of(
+ "name", "test",
+ "floatValue", Float.POSITIVE_INFINITY,
+ "doubleValue", Double.POSITIVE_INFINITY);
+ mapping = MergedAnnotation.of(RequestMapping.class, attributes).synthesize();
+ assertThat(mapping).asString().contains("floatValue=1.0f/0.0f", "doubleValue=1.0/0.0");
+
+ attributes = Map.of(
+ "name", "test",
+ "floatValue", Float.NEGATIVE_INFINITY,
+ "doubleValue", Double.NEGATIVE_INFINITY);
+ mapping = MergedAnnotation.of(RequestMapping.class, attributes).synthesize();
+ assertThat(mapping).asString().contains("floatValue=-1.0f/0.0f", "doubleValue=-1.0/0.0");
+ }
+
@Test
void equalsForSynthesizedAnnotations() throws Exception {
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
diff --git a/spring-core/src/test/java/org/springframework/util/backoff/ExponentialBackOffTests.java b/spring-core/src/test/java/org/springframework/util/backoff/ExponentialBackOffTests.java
index 39ea225f2ed..01517e0216f 100644
--- a/spring-core/src/test/java/org/springframework/util/backoff/ExponentialBackOffTests.java
+++ b/spring-core/src/test/java/org/springframework/util/backoff/ExponentialBackOffTests.java
@@ -79,7 +79,17 @@ class ExponentialBackOffTests {
}
@Test
- void maxAttemptsReached() {
+ void maxIntervalReachedImmediately() {
+ ExponentialBackOff backOff = new ExponentialBackOff(1000L, 2.0);
+ backOff.setMaxInterval(50L);
+
+ BackOffExecution execution = backOff.start();
+ assertThat(execution.nextBackOff()).isEqualTo(50L);
+ assertThat(execution.nextBackOff()).isEqualTo(50L);
+ }
+
+ @Test
+ void maxElapsedTimeReached() {
ExponentialBackOff backOff = new ExponentialBackOff(2000L, 2.0);
backOff.setMaxElapsedTime(4000L);
@@ -90,6 +100,19 @@ class ExponentialBackOffTests {
assertThat(execution.nextBackOff()).isEqualTo(BackOffExecution.STOP);
}
+ @Test
+ void maxAttempts() {
+ ExponentialBackOff backOff = new ExponentialBackOff();
+ backOff.setInitialInterval(1000L);
+ backOff.setMultiplier(2.0);
+ backOff.setMaxInterval(10000L);
+ backOff.setMaxAttempts(6);
+ List delays = new ArrayList<>();
+ BackOffExecution execution = backOff.start();
+ IntStream.range(0, 7).forEach(i -> delays.add(execution.nextBackOff()));
+ assertThat(delays).containsExactly(1000L, 2000L, 4000L, 8000L, 10000L, 10000L, BackOffExecution.STOP);
+ }
+
@Test
void startReturnsDifferentInstances() {
ExponentialBackOff backOff = new ExponentialBackOff();
@@ -143,16 +166,6 @@ class ExponentialBackOffTests {
assertThat(execution.nextBackOff()).isBetween(4275L, 4724L);
}
- @Test
- void maxIntervalReachedImmediately() {
- ExponentialBackOff backOff = new ExponentialBackOff(1000L, 2.0);
- backOff.setMaxInterval(50L);
-
- BackOffExecution execution = backOff.start();
- assertThat(execution.nextBackOff()).isEqualTo(50L);
- assertThat(execution.nextBackOff()).isEqualTo(50L);
- }
-
@Test
void toStringContent() {
ExponentialBackOff backOff = new ExponentialBackOff(2000L, 2.0);
@@ -174,17 +187,4 @@ class ExponentialBackOffTests {
assertThat(execution).asString().isEqualTo("ExponentialBackOffExecution[currentInterval=4000ms, multiplier=2.0, attempts=2]");
}
- @Test
- void maxAttempts() {
- ExponentialBackOff backOff = new ExponentialBackOff();
- backOff.setInitialInterval(1000L);
- backOff.setMultiplier(2.0);
- backOff.setMaxInterval(10000L);
- backOff.setMaxAttempts(6);
- List delays = new ArrayList<>();
- BackOffExecution execution = backOff.start();
- IntStream.range(0, 7).forEach(i -> delays.add(execution.nextBackOff()));
- assertThat(delays).containsExactly(1000L, 2000L, 4000L, 8000L, 10000L, 10000L, BackOffExecution.STOP);
- }
-
}