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.collections.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</code>.
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 * Null keys are supported.
33 * <p>
34 * The <code>keySet()</code> method returns all lowercase keys, or nulls.
35 * <p>
36 * Example:
37 * <pre><code>
38 * Map<String, String> map = new CaseInsensitiveMap<String, String>();
39 * map.put("One", "One");
40 * map.put("Two", "Two");
41 * map.put(null, "Three");
42 * map.put("one", "Four");
43 * </code></pre>
44 * creates a <code>CaseInsensitiveMap</code> with three entries.<br>
45 * <code>map.get(null)</code> returns <code>"Three"</code> and <code>map.get("ONE")</code>
46 * returns <code>"Four".</code> The <code>Set</code> returned by <code>keySet()</code>
47 * equals <code>{"one", "two", null}.</code>
48 * <p>
49 * <strong>This map will violate the detail of various Map and map view contracts.</note>
50 * As a general rule, don't compare this map to other maps. In particular, you can't
51 * use decorators like {@link ListOrderedMap} on it, which silently assume that these
52 * contracts are fulfilled.
53 * <p>
54 * <strong>Note that CaseInsensitiveMap is not synchronized and is not thread-safe.</strong>
55 * If you wish to use this map from multiple threads concurrently, you must use
56 * appropriate synchronization. The simplest approach is to wrap this map
57 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
58 * exceptions when accessed by concurrent threads without synchronization.
59 *
60 * @since 3.0
61 * @version $Id: CaseInsensitiveMap.java 1436463 2013-01-21 16:35:01Z tn $
62 */
63 public class CaseInsensitiveMap<K, V> extends AbstractHashedMap<K, V> implements Serializable, Cloneable {
64
65 /** Serialisation version */
66 private static final long serialVersionUID = -7074655917369299456L;
67
68 /**
69 * Constructs a new empty map with default size and load factor.
70 */
71 public CaseInsensitiveMap() {
72 super(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_THRESHOLD);
73 }
74
75 /**
76 * Constructs a new, empty map with the specified initial capacity.
77 *
78 * @param initialCapacity the initial capacity
79 * @throws IllegalArgumentException if the initial capacity is negative
80 */
81 public CaseInsensitiveMap(final int initialCapacity) {
82 super(initialCapacity);
83 }
84
85 /**
86 * Constructs a new, empty map with the specified initial capacity and
87 * load factor.
88 *
89 * @param initialCapacity the initial capacity
90 * @param loadFactor the load factor
91 * @throws IllegalArgumentException if the initial capacity is negative
92 * @throws IllegalArgumentException if the load factor is less than zero
93 */
94 public CaseInsensitiveMap(final int initialCapacity, final float loadFactor) {
95 super(initialCapacity, loadFactor);
96 }
97
98 /**
99 * Constructor copying elements from another map.
100 * <p>
101 * Keys will be converted to lower case strings, which may cause
102 * some entries to be removed (if string representation of keys differ
103 * only by character case).
104 *
105 * @param map the map to copy
106 * @throws NullPointerException if the map is null
107 */
108 public CaseInsensitiveMap(final Map<K, V> map) {
109 super(map);
110 }
111
112 //-----------------------------------------------------------------------
113 /**
114 * Overrides convertKey() from {@link AbstractHashedMap} to convert keys to
115 * lower case.
116 * <p>
117 * Returns {@link AbstractHashedMap#NULL} if key is null.
118 *
119 * @param key the key convert
120 * @return the converted key
121 */
122 @Override
123 protected Object convertKey(final Object key) {
124 if (key != null) {
125 final char[] chars = key.toString().toCharArray();
126 for (int i = chars.length - 1; i >= 0; i--) {
127 chars[i] = Character.toLowerCase(Character.toUpperCase(chars[i]));
128 }
129 return new String(chars);
130 }
131 return AbstractHashedMap.NULL;
132 }
133
134 //-----------------------------------------------------------------------
135 /**
136 * Clones the map without cloning the keys or values.
137 *
138 * @return a shallow clone
139 */
140 @Override
141 public CaseInsensitiveMap<K, V> clone() {
142 return (CaseInsensitiveMap<K, V>) super.clone();
143 }
144
145 /**
146 * Write the map out using a custom routine.
147 */
148 private void writeObject(final ObjectOutputStream out) throws IOException {
149 out.defaultWriteObject();
150 doWriteObject(out);
151 }
152
153 /**
154 * Read the map in using a custom routine.
155 */
156 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
157 in.defaultReadObject();
158 doReadObject(in);
159 }
160
161 }