diff --git a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java index b221ca9a265..0a2da8c73f0 100644 --- a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java +++ b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java @@ -222,17 +222,22 @@ public class LinkedCaseInsensitiveMap implements Map @Override public @Nullable V computeIfAbsent(String key, Function mappingFunction) { - String oldKey = this.caseInsensitiveKeys.putIfAbsent(convertKey(key), key); + String convertedKey = convertKey(key); + String oldKey = this.caseInsensitiveKeys.get(convertedKey); if (oldKey != null) { V oldKeyValue = this.targetMap.get(oldKey); if (oldKeyValue != null) { return oldKeyValue; } - else { - key = oldKey; - } + return this.targetMap.computeIfAbsent(oldKey, mappingFunction); } - return this.targetMap.computeIfAbsent(key, mappingFunction); + return this.targetMap.computeIfAbsent(key, k -> { + V value = mappingFunction.apply(k); + if (value != null) { + this.caseInsensitiveKeys.putIfAbsent(convertedKey, k); + } + return value; + }); } @Override diff --git a/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java b/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java index 43840c6168d..6b7e1a86f04 100644 --- a/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java +++ b/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java @@ -22,6 +22,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link LinkedCaseInsensitiveMap}. @@ -106,6 +107,7 @@ class LinkedCaseInsensitiveMapTests { assertThat(map.put("null", null)).isEqualTo("value"); assertThat(map.computeIfAbsent("NULL", s -> "value")).isEqualTo("value"); assertThat(map.get("null")).isEqualTo("value"); + assertThat(map.keySet()).containsExactly("Key", "null"); } @Test @@ -115,6 +117,50 @@ class LinkedCaseInsensitiveMapTests { assertThat(map.computeIfAbsent("Key", key -> "value3")).isEqualTo("value1"); } + @Test + void computeIfAbsentWithNullComputedValue() { + assertThat(map.computeIfAbsent("Key", key -> null)).isNull(); + assertThat(map).isEmpty(); + assertThat(map.containsKey("key")).isFalse(); + } + + @Test + void computeIfAbsentAfterNullComputedValueUsesGivenKey() { + assertThat(map.computeIfAbsent("Key", key -> null)).isNull(); + assertThat(map.computeIfAbsent("KEY", key -> "value")).isEqualTo("value"); + assertThat(map.keySet()).containsExactly("KEY"); + } + + @Test + void computeIfAbsentWithFailingMappingFunction() { + assertThatIllegalStateException().isThrownBy(() -> + map.computeIfAbsent("Key", key -> { throw new IllegalStateException(); })); + assertThat(map).isEmpty(); + assertThat(map.containsKey("key")).isFalse(); + } + + @Test + void computeIfAbsentWithNullComputedValueForExistingNullValue() { + assertThat(map.put("Key", null)).isNull(); + assertThat(map.computeIfAbsent("KEY", key -> null)).isNull(); + assertThat(map).hasSize(1); + assertThat(map.containsKey("key")).isTrue(); + assertThat(map.keySet()).containsExactly("Key"); + } + + @Test + void computeIfAbsentWithImmediatelyEvictedEntry() { + LinkedCaseInsensitiveMap evictingMap = new LinkedCaseInsensitiveMap<>() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return true; + } + }; + assertThat(evictingMap.computeIfAbsent("Key", key -> "value")).isEqualTo("value"); + assertThat(evictingMap).isEmpty(); + assertThat(evictingMap.containsKey("key")).isFalse(); + } + @Test void mapClone() { assertThat(map.put("key", "value1")).isNull();