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.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&lt;String, String&gt; map = new CaseInsensitiveMap&lt;String, String&gt;();
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 }