001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.commons.collections.map;
018    
019    import java.io.IOException;
020    import java.io.ObjectInputStream;
021    import java.io.ObjectOutputStream;
022    import java.io.Serializable;
023    import java.util.Map;
024    
025    /**
026     * A case-insensitive <code>Map</code>.
027     * <p>
028     * As entries are added to the map, keys are converted to all lowercase. A new 
029     * key is compared to existing keys by comparing <code>newKey.toString().toLower()</code>
030     * to the lowercase values in the current <code>KeySet.</code>
031     * <p>
032     * Null keys are supported.  
033     * <p>
034     * The <code>keySet()</code> method returns all lowercase keys, or nulls.
035     * <p>
036     * Example:
037     * <pre><code>
038     *  Map map = new CaseInsensitiveMap();
039     *  map.put("One", "One");
040     *  map.put("Two", "Two");
041     *  map.put(null, "Three");
042     *  map.put("one", "Four");
043     * </code></pre>
044     * creates a <code>CaseInsensitiveMap</code> with three entries.<br>
045     * <code>map.get(null)</code> returns <code>"Three"</code> and <code>map.get("ONE")</code>
046     * returns <code>"Four".</code>  The <code>Set</code> returned by <code>keySet()</code>
047     * equals <code>{"one", "two", null}.</code>
048     * <p>
049     * <strong>Note that CaseInsensitiveMap is not synchronized and is not thread-safe.</strong>
050     * If you wish to use this map from multiple threads concurrently, you must use
051     * appropriate synchronization. The simplest approach is to wrap this map
052     * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw 
053     * exceptions when accessed by concurrent threads without synchronization.
054     *
055     * @since Commons Collections 3.0
056     * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
057     *
058     * @author Commons-Collections team
059     */
060    public class CaseInsensitiveMap extends AbstractHashedMap implements Serializable, Cloneable {
061    
062        /** Serialisation version */
063        private static final long serialVersionUID = -7074655917369299456L;
064    
065        /**
066         * Constructs a new empty map with default size and load factor.
067         */
068        public CaseInsensitiveMap() {
069            super(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_THRESHOLD);
070        }
071    
072        /**
073         * Constructs a new, empty map with the specified initial capacity. 
074         *
075         * @param initialCapacity  the initial capacity
076         * @throws IllegalArgumentException if the initial capacity is less than one
077         */
078        public CaseInsensitiveMap(int initialCapacity) {
079            super(initialCapacity);
080        }
081    
082        /**
083         * Constructs a new, empty map with the specified initial capacity and
084         * load factor. 
085         *
086         * @param initialCapacity  the initial capacity
087         * @param loadFactor  the load factor
088         * @throws IllegalArgumentException if the initial capacity is less than one
089         * @throws IllegalArgumentException if the load factor is less than zero
090         */
091        public CaseInsensitiveMap(int initialCapacity, float loadFactor) {
092            super(initialCapacity, loadFactor);
093        }
094    
095        /**
096         * Constructor copying elements from another map.
097         * <p>
098         * Keys will be converted to lower case strings, which may cause
099         * some entries to be removed (if string representation of keys differ
100         * only by character case).
101         *
102         * @param map  the map to copy
103         * @throws NullPointerException if the map is null
104         */
105        public CaseInsensitiveMap(Map map) {
106            super(map);
107        }
108    
109        //-----------------------------------------------------------------------
110        /**
111         * Overrides convertKey() from {@link AbstractHashedMap} to convert keys to 
112         * lower case.
113         * <p>
114         * Returns null if key is null.
115         * 
116         * @param key  the key convert
117         * @return the converted key
118         */
119        protected Object convertKey(Object key) {
120            if (key != null) {
121                return key.toString().toLowerCase();
122            } else {
123                return AbstractHashedMap.NULL;
124            }
125        }   
126    
127        //-----------------------------------------------------------------------
128        /**
129         * Clones the map without cloning the keys or values.
130         *
131         * @return a shallow clone
132         */
133        public Object clone() {
134            return super.clone();
135        }
136    
137        /**
138         * Write the map out using a custom routine.
139         */
140        private void writeObject(ObjectOutputStream out) throws IOException {
141            out.defaultWriteObject();
142            doWriteObject(out);
143        }
144    
145        /**
146         * Read the map in using a custom routine.
147         */
148        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
149            in.defaultReadObject();
150            doReadObject(in);
151        }
152     
153    }