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.camel.util;
018    
019    import java.util.HashMap;
020    import java.util.HashSet;
021    import java.util.Map;
022    import java.util.Set;
023    
024    /**
025     * A map that uses case insensitive keys, but preserves the original keys in the keySet.
026     * <p/>
027     * This map allows you to do lookup using case insenstive keys so you can retrieve the value without worring about
028     * whether some transport protocol affects the keys such as Http and Mail protocols can do.
029     * <p/>
030     * When copying from this map to a regular Map such as {@link java.util.HashMap} then the original keys are
031     * copied over and you get the old behavior back using a regular Map with case sensitive keys.
032     *
033     * @version $Revision: 883598 $
034     */
035    public class CaseInsensitiveMap extends HashMap<String, Object> {
036        private static final long serialVersionUID = -8538318195477618308L;
037    
038        // holds a map of lower case key -> original key
039        private Map<String, String> originalKeys;
040        // holds a snapshot view of current entry set
041        private transient Set<Map.Entry<String, Object>> entrySetView;
042    
043        public CaseInsensitiveMap() {
044            super();
045            originalKeys = new HashMap<String, String>();
046        }
047    
048        public CaseInsensitiveMap(Map<? extends String, ?> map) {
049            this();
050            putAll(map);
051        }
052    
053        public CaseInsensitiveMap(int initialCapacity, float loadFactor) {
054            super(initialCapacity, loadFactor);
055            originalKeys = new HashMap<String, String>(initialCapacity);
056        }
057    
058        public CaseInsensitiveMap(int initialCapacity) {
059            super(initialCapacity);
060            originalKeys = new HashMap<String, String>();
061        }
062    
063        @Override
064        public Object get(Object key) {
065            String s = key.toString().toLowerCase();
066            Object answer = super.get(s);
067            if (answer == null) {
068                // fallback to lookup by original key
069                String originalKey = originalKeys.get(s);
070                answer = super.get(originalKey);
071            }
072            return answer;
073        }
074    
075        @Override
076        public Object put(String key, Object value) {
077            // invalidate views as we mutate
078            entrySetView = null;
079            String s = key.toLowerCase();
080            originalKeys.put(s, key);
081            return super.put(s, value);
082        }
083    
084        @Override
085        public void putAll(Map<? extends String, ?> map) {
086            for (Map.Entry<? extends String, ?> entry : map.entrySet()) {
087                put(entry.getKey(), entry.getValue());
088            }
089        }
090    
091        @Override
092        public Object remove(Object key) {
093            // invalidate views as we mutate
094            entrySetView = null;
095            String s = key.toString().toLowerCase();
096            originalKeys.remove(s);
097            return super.remove(s);
098        }
099    
100        @Override
101        public void clear() {
102            // invalidate views as we mutate
103            entrySetView = null;
104            originalKeys.clear();
105            super.clear();
106        }
107    
108        @Override
109        public boolean containsKey(Object key) {
110            String s = key.toString().toLowerCase();
111            return super.containsKey(s);
112        }
113    
114        @Override
115        public Set<Map.Entry<String, Object>> entrySet() {
116            if (entrySetView == null) {
117                // build the key set using the original keys so we retain their case
118                // when for example we copy values to another map
119                entrySetView = new HashSet<Map.Entry<String, Object>>(this.size());
120                for (final Map.Entry<String, Object> entry : super.entrySet()) {
121                    Map.Entry<String, Object> view = new Map.Entry<String, Object>() {
122                        public String getKey() {
123                            String s = entry.getKey();
124                            // use the original key so we can preserve it
125                            return originalKeys.get(s);
126                        }
127    
128                        public Object getValue() {
129                            return entry.getValue();
130                        }
131    
132                        public Object setValue(Object o) {
133                            return entry.setValue(o);
134                        }
135                    };
136                    entrySetView.add(view);
137                }
138            }
139    
140            return entrySetView;
141        }
142    }