Skip to content

Commit 505480c

Browse files
committed
LinkedCaseInsensitiveMap delegates to LinkedHashMap instead of extending it
Issue: SPR-15026 (cherry picked from commit 8147c11)
1 parent fa2bfdd commit 505480c

File tree

3 files changed

+125
-35
lines changed

3 files changed

+125
-35
lines changed

spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java

Lines changed: 93 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616

1717
package org.springframework.util;
1818

19+
import java.io.Serializable;
20+
import java.util.Collection;
1921
import java.util.HashMap;
2022
import java.util.LinkedHashMap;
2123
import java.util.Locale;
2224
import java.util.Map;
25+
import java.util.Set;
2326

2427
/**
2528
* {@link LinkedHashMap} variant that stores String keys in a case-insensitive
@@ -34,9 +37,11 @@
3437
* @since 3.0
3538
*/
3639
@SuppressWarnings("serial")
37-
public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
40+
public class LinkedCaseInsensitiveMap<V> implements Map<String, V>, Serializable, Cloneable {
3841

39-
private Map<String, String> caseInsensitiveKeys;
42+
private final LinkedHashMap<String, V> targetMap;
43+
44+
private final HashMap<String, String> caseInsensitiveKeys;
4045

4146
private final Locale locale;
4247

@@ -46,7 +51,7 @@ public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
4651
* @see java.lang.String#toLowerCase()
4752
*/
4853
public LinkedCaseInsensitiveMap() {
49-
this(null);
54+
this((Locale) null);
5055
}
5156

5257
/**
@@ -56,9 +61,7 @@ public LinkedCaseInsensitiveMap() {
5661
* @see java.lang.String#toLowerCase(java.util.Locale)
5762
*/
5863
public LinkedCaseInsensitiveMap(Locale locale) {
59-
super();
60-
this.caseInsensitiveKeys = new HashMap<String, String>();
61-
this.locale = (locale != null ? locale : Locale.getDefault());
64+
this(16, locale);
6265
}
6366

6467
/**
@@ -81,19 +84,68 @@ public LinkedCaseInsensitiveMap(int initialCapacity) {
8184
* @see java.lang.String#toLowerCase(java.util.Locale)
8285
*/
8386
public LinkedCaseInsensitiveMap(int initialCapacity, Locale locale) {
84-
super(initialCapacity);
87+
this.targetMap = new LinkedHashMap<String, V>(initialCapacity) {
88+
@Override
89+
protected boolean removeEldestEntry(Map.Entry<String, V> eldest) {
90+
boolean doRemove = LinkedCaseInsensitiveMap.this.removeEldestEntry(eldest);
91+
if (doRemove) {
92+
caseInsensitiveKeys.remove(convertKey(eldest.getKey()));
93+
}
94+
return doRemove;
95+
}
96+
};
8597
this.caseInsensitiveKeys = new HashMap<String, String>(initialCapacity);
8698
this.locale = (locale != null ? locale : Locale.getDefault());
8799
}
88100

101+
/**
102+
* Copy constructor.
103+
*/
104+
@SuppressWarnings("unchecked")
105+
private LinkedCaseInsensitiveMap(LinkedCaseInsensitiveMap<V> other) {
106+
this.targetMap = (LinkedHashMap<String, V>) other.targetMap.clone();
107+
this.caseInsensitiveKeys = (HashMap<String, String>) other.caseInsensitiveKeys.clone();
108+
this.locale = other.locale;
109+
}
110+
111+
112+
@Override
113+
public int size() {
114+
return this.targetMap.size();
115+
}
116+
117+
@Override
118+
public boolean isEmpty() {
119+
return this.targetMap.isEmpty();
120+
}
121+
122+
@Override
123+
public boolean containsValue(Object value) {
124+
return this.targetMap.containsValue(value);
125+
}
126+
127+
@Override
128+
public Set<String> keySet() {
129+
return this.targetMap.keySet();
130+
}
131+
132+
@Override
133+
public Collection<V> values() {
134+
return this.targetMap.values();
135+
}
136+
137+
@Override
138+
public Set<Entry<String, V>> entrySet() {
139+
return this.targetMap.entrySet();
140+
}
89141

90142
@Override
91143
public V put(String key, V value) {
92144
String oldKey = this.caseInsensitiveKeys.put(convertKey(key), key);
93145
if (oldKey != null && !oldKey.equals(key)) {
94-
super.remove(oldKey);
146+
this.targetMap.remove(oldKey);
95147
}
96-
return super.put(key, value);
148+
return this.targetMap.put(key, value);
97149
}
98150

99151
@Override
@@ -116,19 +168,18 @@ public V get(Object key) {
116168
if (key instanceof String) {
117169
String caseInsensitiveKey = this.caseInsensitiveKeys.get(convertKey((String) key));
118170
if (caseInsensitiveKey != null) {
119-
return super.get(caseInsensitiveKey);
171+
return this.targetMap.get(caseInsensitiveKey);
120172
}
121173
}
122174
return null;
123175
}
124176

125-
// Overridden to avoid LinkedHashMap's own hash computation in its getOrDefault impl
126177
@Override
127178
public V getOrDefault(Object key, V defaultValue) {
128179
if (key instanceof String) {
129180
String caseInsensitiveKey = this.caseInsensitiveKeys.get(convertKey((String) key));
130181
if (caseInsensitiveKey != null) {
131-
return super.get(caseInsensitiveKey);
182+
return this.targetMap.get(caseInsensitiveKey);
132183
}
133184
}
134185
return defaultValue;
@@ -139,7 +190,7 @@ public V remove(Object key) {
139190
if (key instanceof String) {
140191
String caseInsensitiveKey = this.caseInsensitiveKeys.remove(convertKey((String) key));
141192
if (caseInsensitiveKey != null) {
142-
return super.remove(caseInsensitiveKey);
193+
return this.targetMap.remove(caseInsensitiveKey);
143194
}
144195
}
145196
return null;
@@ -148,15 +199,28 @@ public V remove(Object key) {
148199
@Override
149200
public void clear() {
150201
this.caseInsensitiveKeys.clear();
151-
super.clear();
202+
this.targetMap.clear();
152203
}
153204

205+
154206
@Override
155-
@SuppressWarnings("unchecked")
156-
public Object clone() {
157-
LinkedCaseInsensitiveMap<V> copy = (LinkedCaseInsensitiveMap<V>) super.clone();
158-
copy.caseInsensitiveKeys = new HashMap<String, String>(this.caseInsensitiveKeys);
159-
return copy;
207+
public LinkedCaseInsensitiveMap<V> clone() {
208+
return new LinkedCaseInsensitiveMap<V>(this);
209+
}
210+
211+
@Override
212+
public boolean equals(Object obj) {
213+
return this.targetMap.equals(obj);
214+
}
215+
216+
@Override
217+
public int hashCode() {
218+
return this.targetMap.hashCode();
219+
}
220+
221+
@Override
222+
public String toString() {
223+
return this.targetMap.toString();
160224
}
161225

162226

@@ -172,4 +236,14 @@ protected String convertKey(String key) {
172236
return key.toLowerCase(this.locale);
173237
}
174238

239+
/**
240+
* Determine whether this map should remove the given eldest entry.
241+
* @param eldest the candidate entry
242+
* @return {@code true} for removing it, {@code false} for keeping it
243+
* @see LinkedHashMap#removeEldestEntry
244+
*/
245+
protected boolean removeEldestEntry(Map.Entry<String, V> eldest) {
246+
return false;
247+
}
248+
175249
}

spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@
3535
* @author Juergen Hoeller
3636
* @since 3.0
3737
*/
38-
public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializable {
38+
public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializable, Cloneable {
3939

4040
private static final long serialVersionUID = 3801124242820219131L;
4141

@@ -176,18 +176,6 @@ public Set<Entry<K, List<V>>> entrySet() {
176176
}
177177

178178

179-
/**
180-
* Create a regular copy of this Map.
181-
* @return a shallow copy of this Map, reusing this Map's value-holding List entries
182-
* @since 4.2
183-
* @see LinkedMultiValueMap#LinkedMultiValueMap(Map)
184-
* @see #deepCopy()
185-
*/
186-
@Override
187-
public LinkedMultiValueMap<K, V> clone() {
188-
return new LinkedMultiValueMap<K, V>(this);
189-
}
190-
191179
/**
192180
* Create a deep copy of this Map.
193181
* @return a copy of this Map, including a copy of each value-holding List entry
@@ -202,6 +190,17 @@ public LinkedMultiValueMap<K, V> deepCopy() {
202190
return copy;
203191
}
204192

193+
/**
194+
* Create a regular copy of this Map.
195+
* @return a shallow copy of this Map, reusing this Map's value-holding List entries
196+
* @since 4.2
197+
* @see LinkedMultiValueMap#LinkedMultiValueMap(Map)
198+
* @see #deepCopy()
199+
*/
200+
@Override
201+
public LinkedMultiValueMap<K, V> clone() {
202+
return new LinkedMultiValueMap<K, V>(this);
203+
}
205204

206205
@Override
207206
public boolean equals(Object obj) {

spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*/
2626
public class LinkedCaseInsensitiveMapTests {
2727

28-
private final LinkedCaseInsensitiveMap<String> map = new LinkedCaseInsensitiveMap<String>();
28+
private final LinkedCaseInsensitiveMap<String> map = new LinkedCaseInsensitiveMap<>();
2929

3030

3131
@Test
@@ -74,11 +74,28 @@ public void getOrDefaultWithNullValue() {
7474
assertEquals("N", map.getOrDefault(new Object(), "N"));
7575
}
7676

77+
@Test
78+
public void computeIfAbsentWithExistingValue() {
79+
map.put("key", "value1");
80+
map.put("KEY", "value2");
81+
map.put("Key", "value3");
82+
assertEquals("value3", map.computeIfAbsent("key", key -> "value1"));
83+
assertEquals("value3", map.computeIfAbsent("KEY", key -> "value2"));
84+
assertEquals("value3", map.computeIfAbsent("Key", key -> "value3"));
85+
}
86+
87+
@Test
88+
public void computeIfAbsentWithComputedValue() {
89+
assertEquals("value1", map.computeIfAbsent("key", key -> "value1"));
90+
assertEquals("value1", map.computeIfAbsent("KEY", key -> "value2"));
91+
assertEquals("value1", map.computeIfAbsent("Key", key -> "value3"));
92+
}
93+
7794
@Test
7895
@SuppressWarnings("unchecked")
7996
public void mapClone() {
8097
map.put("key", "value1");
81-
LinkedCaseInsensitiveMap<String> copy = (LinkedCaseInsensitiveMap<String>) map.clone();
98+
LinkedCaseInsensitiveMap<String> copy = map.clone();
8299
assertEquals("value1", map.get("key"));
83100
assertEquals("value1", map.get("KEY"));
84101
assertEquals("value1", map.get("Key"));

0 commit comments

Comments
 (0)