1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.collections4.map;
18
19 import java.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.io.ObjectOutputStream;
22 import java.io.Serializable;
23 import java.util.Map;
24
25 /**
26 * A case-insensitive {@code Map}.
27 * <p>
28 * Before keys are added to the map or compared to other existing keys, they are converted
29 * to all lowercase in a locale-independent fashion by using information from the Unicode
30 * data file.
31 * </p>
32 * <p>
33 * Null keys are supported.
34 * </p>
35 * <p>
36 * The {@code keySet()} method returns all lowercase keys, or nulls.
37 * </p>
38 * <p>
39 * Example:
40 * </p>
41 * <pre><code>
42 * Map<String, String> map = new CaseInsensitiveMap<String, String>();
43 * map.put("One", "One");
44 * map.put("Two", "Two");
45 * map.put(null, "Three");
46 * map.put("one", "Four");
47 * </code></pre>
48 * <p>
49 * The example above creates a {@code CaseInsensitiveMap} with three entries.
50 * </p>
51 * <p>
52 * {@code map.get(null)} returns {@code "Three"} and {@code map.get("ONE")}
53 * returns {@code "Four".} The {@code Set} returned by {@code keySet()}
54 * equals {@code {"one", "two", null}.}
55 * </p>
56 * <p>
57 * <strong>This map will violate the detail of various Map and map view contracts.</strong>
58 * As a general rule, don't compare this map to other maps. In particular, you can't
59 * use decorators like {@link ListOrderedMap} on it, which silently assume that these
60 * contracts are fulfilled.
61 * </p>
62 * <p>
63 * <strong>Note that CaseInsensitiveMap is not synchronized and is not thread-safe.</strong>
64 * If you wish to use this map from multiple threads concurrently, you must use
65 * appropriate synchronization. The simplest approach is to wrap this map
66 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
67 * exceptions when accessed by concurrent threads without synchronization.
68 * </p>
69 *
70 * @param <K> the type of the keys in this map
71 * @param <V> the type of the values in this map
72 * @since 3.0
73 */
74 public class CaseInsensitiveMap<K, V> extends AbstractHashedMap<K, V> implements Serializable, Cloneable {
75
76 /** Serialization version */
77 private static final long serialVersionUID = -7074655917369299456L;
78
79 /**
80 * Constructs a new empty map with default size and load factor.
81 */
82 public CaseInsensitiveMap() {
83 super(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_THRESHOLD);
84 }
85
86 /**
87 * Constructs a new, empty map with the specified initial capacity.
88 *
89 * @param initialCapacity the initial capacity
90 * @throws IllegalArgumentException if the initial capacity is negative
91 */
92 public CaseInsensitiveMap(final int initialCapacity) {
93 super(initialCapacity);
94 }
95
96 /**
97 * Constructs a new, empty map with the specified initial capacity and
98 * load factor.
99 *
100 * @param initialCapacity the initial capacity
101 * @param loadFactor the load factor
102 * @throws IllegalArgumentException if the initial capacity is negative
103 * @throws IllegalArgumentException if the load factor is less than zero
104 */
105 public CaseInsensitiveMap(final int initialCapacity, final float loadFactor) {
106 super(initialCapacity, loadFactor);
107 }
108
109 /**
110 * Constructor copying elements from another map.
111 * <p>
112 * Keys will be converted to lower case strings, which may cause
113 * some entries to be removed (if string representation of keys differ
114 * only by character case).
115 *
116 * @param map the map to copy
117 * @throws NullPointerException if the map is null
118 */
119 public CaseInsensitiveMap(final Map<? extends K, ? extends V> map) {
120 super(map);
121 }
122
123 /**
124 * Clones the map without cloning the keys or values.
125 *
126 * @return a shallow clone
127 */
128 @Override
129 public CaseInsensitiveMap<K, V> clone() {
130 return (CaseInsensitiveMap<K, V>) super.clone();
131 }
132
133 /**
134 * Overrides convertKey() from {@link AbstractHashedMap} to convert keys to
135 * lower case.
136 * <p>
137 * Returns {@link AbstractHashedMap#NULL} if key is null.
138 *
139 * @param key the key convert
140 * @return the converted key
141 */
142 @Override
143 protected Object convertKey(final Object key) {
144 if (key != null) {
145 final char[] chars = key.toString().toCharArray();
146 for (int i = chars.length - 1; i >= 0; i--) {
147 chars[i] = Character.toLowerCase(Character.toUpperCase(chars[i]));
148 }
149 return new String(chars);
150 }
151 return NULL;
152 }
153
154 /**
155 * Deserializes the map in using a custom routine.
156 *
157 * @param in the input stream
158 * @throws IOException if an error occurs while reading from the stream
159 * @throws ClassNotFoundException if an object read from the stream cannot be loaded
160 */
161 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
162 in.defaultReadObject();
163 doReadObject(in);
164 }
165
166 /**
167 * Serializes this object to an ObjectOutputStream.
168 *
169 * @param out the target ObjectOutputStream.
170 * @throws IOException thrown when an I/O errors occur writing to the target stream.
171 */
172 private void writeObject(final ObjectOutputStream out) throws IOException {
173 out.defaultWriteObject();
174 doWriteObject(out);
175 }
176
177 }