001    package com.mockrunner.util.common;
002    
003    import java.util.Collection;
004    import java.util.HashMap;
005    import java.util.Iterator;
006    import java.util.Map;
007    import java.util.Set;
008    
009    /**
010     * Implementation of a <code>Map</code> that recognizes the case of the
011     * keys, if the keys are strings. If <code>isCaseSensitive</code> is
012     * <code>true</code> it behaves exactly like a <code>HashMap</code>.
013     * If <code>isCaseSensitive</code> is <code>false</code> (which is the
014     * default), it considers same strings with different case as equal.
015     * I.e. if you do
016     * <br>
017     * <br>
018     * <code>put("test", "1");</code>
019     * <br>
020     * <code>put("TEST", "2");</code>
021     * <br>
022     * <br>
023     * the second <code>put</code> overwrites the value of the first one, 
024     * because the keys are considered to be equal. With
025     * <br>
026     * <br>
027     * <code>get("TesT");</code>
028     * <br>
029     * <br>
030     * you'll get the result <code>"2"</code>.
031     * If you iterate through the keys (using either <code>keySet</code> or
032     * <code>entrySet</code>), you'll get the first added version of the key,
033     * in the above case, you'll get <code>"test"</code>.
034     * It is allowed to use non-strings as keys. In this case the <code>Map</code>
035     * behaves like a usual <code>HashMap</code>.
036     */
037    public class CaseAwareMap implements Map
038    {
039        private boolean isCaseSensitive;
040        private Map caseInsensitiveMap;
041        private Map actualMap;
042         
043        public CaseAwareMap()
044        {
045            this(false);
046        }
047        
048        public CaseAwareMap(boolean isCaseSensitive)
049        {
050            this.isCaseSensitive = isCaseSensitive;
051            caseInsensitiveMap = new HashMap();
052            actualMap = new HashMap();
053        }
054        
055        /**
056         * Returns if keys are case sensitive. Defaults to <code>false</code>.
057         * @return are keys case sensitive
058         */ 
059        public boolean isCaseSensitive()
060        {
061            return isCaseSensitive;
062        }
063        
064        /**
065         * Sets if keys are case sensitive.
066         * If set to <code>true</code> this implementation behaves like
067         * a <code>HashMap</code>. Please note, that all entries are cleared
068         * when switching case sensitivity. It's not possible to switch
069         * and keep the entries.
070         * @param isCaseSensitive are keys case sensitive
071         */ 
072        public void setCaseSensitive(boolean isCaseSensitive)
073        {
074            clear();
075            this.isCaseSensitive = isCaseSensitive;
076        }
077        
078        public void clear()
079        {
080            caseInsensitiveMap.clear();
081            actualMap.clear();
082        }
083        
084        public boolean containsKey(Object key)
085        {
086            Object compareKey = getCompareKey(key);
087            return getCompareMap().containsKey(compareKey);
088        }
089        
090        public boolean containsValue(Object value)
091        {
092            return actualMap.containsValue(value);
093        }
094        
095        public Set entrySet()
096        {
097            return actualMap.entrySet();
098        }
099        
100        public Object get(Object key)
101        {
102            Object compareKey = getCompareKey(key);
103            return getCompareMap().get(compareKey);
104        }
105        
106        public boolean isEmpty()
107        {
108            return size() <= 0;
109        }
110        
111        public Set keySet()
112        {
113            return actualMap.keySet();
114        }
115        
116        public Object put(Object key, Object value)
117        {
118            return doConsistentModify(key, new ConsistentPut(value));
119        }
120        
121        public void putAll(Map map)
122        {
123            Iterator keys = map.keySet().iterator();
124            while(keys.hasNext())
125            {
126                Object nextKey = keys.next();
127                Object nextValue = map.get(nextKey);
128                put(nextKey, nextValue);
129            }
130        }
131        
132        public Object remove(Object key)
133        {
134            return doConsistentModify(key, new ConsistentRemove());
135        }
136        
137        public int size()
138        {
139            return actualMap.size();
140        }
141        
142        public Collection values()
143        {
144            return actualMap.values();
145        }
146        
147        private boolean areKeysEquals(Object actualKey, Object compareKey)
148        {
149            if(null == actualKey && null == compareKey) return true;
150            if(null == actualKey) return false;
151            if(null == compareKey) return false;
152            Object actualCompareKey = getCompareKey(actualKey);
153            return compareKey.equals(actualCompareKey);
154        }
155        
156        private boolean isStringKey(Object key)
157        {
158            return (null != key) && (key instanceof String);
159        }
160        
161        private Object getCompareKey(Object key)
162        {
163            if(isCaseSensitive || !isStringKey(key))
164            {
165                return key;
166            }
167            return ((String)key).toUpperCase();
168        }
169        
170        private Map getCompareMap()
171        {
172            if(isCaseSensitive)
173            {
174                return actualMap;
175            }
176            return caseInsensitiveMap;
177        }
178        
179        private Object doConsistentModify(Object key, ConsistentModify modifier)
180        {
181            Object compareKey = getCompareKey(key);
182            if(!caseInsensitiveMap.containsKey(compareKey))
183            {
184                return modifier.modify(key, compareKey);
185            }
186            Iterator iterator = actualMap.keySet().iterator();
187            while(iterator.hasNext())
188            {
189                Object actualKey = iterator.next();
190                if(areKeysEquals(actualKey, compareKey))
191                {
192                    return modifier.modify(actualKey, compareKey);
193                }
194            }
195            return null;
196        }
197        
198        private interface ConsistentModify
199        {
200            public Object modify(Object key1, Object key2);
201        }
202        
203        private class ConsistentRemove implements ConsistentModify
204        {
205            public Object modify(Object key1, Object key2)
206            {
207                actualMap.remove(key1);
208                return caseInsensitiveMap.remove(key2);
209            }
210        }
211        
212        private class ConsistentPut implements ConsistentModify
213        {
214            private Object value;
215            
216            public ConsistentPut(Object value)
217            {
218                this.value = value;
219            }
220            
221            public Object modify(Object key1, Object key2)
222            {
223                actualMap.put(key1, value);
224                return caseInsensitiveMap.put(key2, value);
225            }
226        }
227    }