View Javadoc
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&lt;String, String&gt; map = new CaseInsensitiveMap&lt;String, String&gt;();
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      /** Serialisation 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 AbstractHashedMap.NULL;
152     }
153 
154     /**
155      * Read 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 can not be loaded
160      */
161     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
162         in.defaultReadObject();
163         doReadObject(in);
164     }
165 
166     /**
167      * Write the map out using a custom routine.
168      *
169      * @param out  the output stream
170      * @throws IOException if an error occurs while writing to the stream
171      */
172     private void writeObject(final ObjectOutputStream out) throws IOException {
173         out.defaultWriteObject();
174         doWriteObject(out);
175     }
176 
177 }