Browse Source

LinkedCaseInsensitiveMap delegates to LinkedHashMap instead of extending it

Issue: SPR-15026
pull/1026/merge
Juergen Hoeller 8 years ago
parent
commit
8147c112f5
  1. 112
      spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java
  2. 25
      spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java
  3. 19
      spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java

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

@ -16,10 +16,13 @@ @@ -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; @@ -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> { @@ -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> { @@ -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> { @@ -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> { @@ -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> { @@ -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> { @@ -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
@SuppressWarnings("unchecked")
public Object clone() {
LinkedCaseInsensitiveMap<V> copy = (LinkedCaseInsensitiveMap<V>) super.clone();
copy.caseInsensitiveKeys = new HashMap<>(this.caseInsensitiveKeys);
return copy;
public LinkedCaseInsensitiveMap<V> clone() {
return new LinkedCaseInsensitiveMap<>(this);
}
@Override
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> { @@ -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;
}
}

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

@ -35,7 +35,7 @@ import java.util.Set; @@ -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 @@ -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 @@ -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) {

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

@ -74,11 +74,28 @@ public class LinkedCaseInsensitiveMapTests { @@ -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…
Cancel
Save