1616
1717package org .springframework .util ;
1818
19+ import java .io .Serializable ;
20+ import java .util .Collection ;
1921import java .util .HashMap ;
2022import java .util .LinkedHashMap ;
2123import java .util .Locale ;
2224import java .util .Map ;
25+ import java .util .Set ;
2326
2427/**
2528 * {@link LinkedHashMap} variant that stores String keys in a case-insensitive
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}
0 commit comments