LinkedCaseInsensitiveMap delegates to LinkedHashMap instead of extending it
Issue: SPR-15026
This commit is contained in:
parent
d3f97e3092
commit
8147c112f5
|
@ -16,10 +16,13 @@
|
|||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* {@link LinkedHashMap} variant that stores String keys in a case-insensitive
|
||||
|
@ -34,9 +37,11 @@ import java.util.Map;
|
|||
* @since 3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
|
||||
public class LinkedCaseInsensitiveMap<V> implements Map<String, V>, Serializable, Cloneable {
|
||||
|
||||
private Map<String, String> caseInsensitiveKeys;
|
||||
private final LinkedHashMap<String, V> targetMap;
|
||||
|
||||
private final HashMap<String, String> caseInsensitiveKeys;
|
||||
|
||||
private final Locale locale;
|
||||
|
||||
|
@ -46,7 +51,7 @@ public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
|
|||
* @see java.lang.String#toLowerCase()
|
||||
*/
|
||||
public LinkedCaseInsensitiveMap() {
|
||||
this(null);
|
||||
this((Locale) null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,9 +61,7 @@ public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
|
|||
* @see java.lang.String#toLowerCase(java.util.Locale)
|
||||
*/
|
||||
public LinkedCaseInsensitiveMap(Locale locale) {
|
||||
super();
|
||||
this.caseInsensitiveKeys = new HashMap<>();
|
||||
this.locale = (locale != null ? locale : Locale.getDefault());
|
||||
this(16, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,19 +84,68 @@ public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
|
|||
* @see java.lang.String#toLowerCase(java.util.Locale)
|
||||
*/
|
||||
public LinkedCaseInsensitiveMap(int initialCapacity, Locale locale) {
|
||||
super(initialCapacity);
|
||||
this.targetMap = new LinkedHashMap<String, V>(initialCapacity) {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<String, V> eldest) {
|
||||
boolean doRemove = LinkedCaseInsensitiveMap.this.removeEldestEntry(eldest);
|
||||
if (doRemove) {
|
||||
caseInsensitiveKeys.remove(convertKey(eldest.getKey()));
|
||||
}
|
||||
return doRemove;
|
||||
}
|
||||
};
|
||||
this.caseInsensitiveKeys = new HashMap<>(initialCapacity);
|
||||
this.locale = (locale != null ? locale : Locale.getDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private LinkedCaseInsensitiveMap(LinkedCaseInsensitiveMap<V> other) {
|
||||
this.targetMap = (LinkedHashMap<String, V>) other.targetMap.clone();
|
||||
this.caseInsensitiveKeys = (HashMap<String, String>) other.caseInsensitiveKeys.clone();
|
||||
this.locale = other.locale;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.targetMap.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.targetMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return this.targetMap.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return this.targetMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
return this.targetMap.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, V>> entrySet() {
|
||||
return this.targetMap.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(String key, V value) {
|
||||
String oldKey = this.caseInsensitiveKeys.put(convertKey(key), key);
|
||||
if (oldKey != null && !oldKey.equals(key)) {
|
||||
super.remove(oldKey);
|
||||
this.targetMap.remove(oldKey);
|
||||
}
|
||||
return super.put(key, value);
|
||||
return this.targetMap.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -116,19 +168,18 @@ public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
|
|||
if (key instanceof String) {
|
||||
String caseInsensitiveKey = this.caseInsensitiveKeys.get(convertKey((String) key));
|
||||
if (caseInsensitiveKey != null) {
|
||||
return super.get(caseInsensitiveKey);
|
||||
return this.targetMap.get(caseInsensitiveKey);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Overridden to avoid LinkedHashMap's own hash computation in its getOrDefault impl
|
||||
@Override
|
||||
public V getOrDefault(Object key, V defaultValue) {
|
||||
if (key instanceof String) {
|
||||
String caseInsensitiveKey = this.caseInsensitiveKeys.get(convertKey((String) key));
|
||||
if (caseInsensitiveKey != null) {
|
||||
return super.get(caseInsensitiveKey);
|
||||
return this.targetMap.get(caseInsensitiveKey);
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
|
@ -139,7 +190,7 @@ public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
|
|||
if (key instanceof String) {
|
||||
String caseInsensitiveKey = this.caseInsensitiveKeys.remove(convertKey((String) key));
|
||||
if (caseInsensitiveKey != null) {
|
||||
return super.remove(caseInsensitiveKey);
|
||||
return this.targetMap.remove(caseInsensitiveKey);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -148,15 +199,28 @@ public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
|
|||
@Override
|
||||
public void clear() {
|
||||
this.caseInsensitiveKeys.clear();
|
||||
super.clear();
|
||||
this.targetMap.clear();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LinkedCaseInsensitiveMap<V> clone() {
|
||||
return new LinkedCaseInsensitiveMap<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object clone() {
|
||||
LinkedCaseInsensitiveMap<V> copy = (LinkedCaseInsensitiveMap<V>) super.clone();
|
||||
copy.caseInsensitiveKeys = new HashMap<>(this.caseInsensitiveKeys);
|
||||
return copy;
|
||||
public boolean equals(Object obj) {
|
||||
return this.targetMap.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.targetMap.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.targetMap.toString();
|
||||
}
|
||||
|
||||
|
||||
|
@ -172,4 +236,14 @@ public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
|
|||
return key.toLowerCase(this.locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this map should remove the given eldest entry.
|
||||
* @param eldest the candidate entry
|
||||
* @return {@code true} for removing it, {@code false} for keeping it
|
||||
* @see LinkedHashMap#removeEldestEntry
|
||||
*/
|
||||
protected boolean removeEldestEntry(Map.Entry<String, V> eldest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ import java.util.Set;
|
|||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializable {
|
||||
public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializable, Cloneable {
|
||||
|
||||
private static final long serialVersionUID = 3801124242820219131L;
|
||||
|
||||
|
@ -176,18 +176,6 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a regular copy of this Map.
|
||||
* @return a shallow copy of this Map, reusing this Map's value-holding List entries
|
||||
* @since 4.2
|
||||
* @see LinkedMultiValueMap#LinkedMultiValueMap(Map)
|
||||
* @see #deepCopy()
|
||||
*/
|
||||
@Override
|
||||
public LinkedMultiValueMap<K, V> clone() {
|
||||
return new LinkedMultiValueMap<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep copy of this Map.
|
||||
* @return a copy of this Map, including a copy of each value-holding List entry
|
||||
|
@ -202,6 +190,17 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa
|
|||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a regular copy of this Map.
|
||||
* @return a shallow copy of this Map, reusing this Map's value-holding List entries
|
||||
* @since 4.2
|
||||
* @see LinkedMultiValueMap#LinkedMultiValueMap(Map)
|
||||
* @see #deepCopy()
|
||||
*/
|
||||
@Override
|
||||
public LinkedMultiValueMap<K, V> clone() {
|
||||
return new LinkedMultiValueMap<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
|
|
@ -74,11 +74,28 @@ public class LinkedCaseInsensitiveMapTests {
|
|||
assertEquals("N", map.getOrDefault(new Object(), "N"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void computeIfAbsentWithExistingValue() {
|
||||
map.put("key", "value1");
|
||||
map.put("KEY", "value2");
|
||||
map.put("Key", "value3");
|
||||
assertEquals("value3", map.computeIfAbsent("key", key -> "value1"));
|
||||
assertEquals("value3", map.computeIfAbsent("KEY", key -> "value2"));
|
||||
assertEquals("value3", map.computeIfAbsent("Key", key -> "value3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void computeIfAbsentWithComputedValue() {
|
||||
assertEquals("value1", map.computeIfAbsent("key", key -> "value1"));
|
||||
assertEquals("value1", map.computeIfAbsent("KEY", key -> "value2"));
|
||||
assertEquals("value1", map.computeIfAbsent("Key", key -> "value3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void mapClone() {
|
||||
map.put("key", "value1");
|
||||
LinkedCaseInsensitiveMap<String> copy = (LinkedCaseInsensitiveMap<String>) map.clone();
|
||||
LinkedCaseInsensitiveMap<String> copy = map.clone();
|
||||
assertEquals("value1", map.get("key"));
|
||||
assertEquals("value1", map.get("KEY"));
|
||||
assertEquals("value1", map.get("Key"));
|
||||
|
|
Loading…
Reference in New Issue