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 */
017package org.apache.camel.util;
018
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Locale;
022import java.util.Map;
023import java.util.Set;
024
025/**
026 * A map that uses case insensitive keys, but preserves the original keys in the keySet.
027 * <p/>
028 * This map allows you to do lookup using case insensitive keys so you can retrieve the value without worrying about
029 * whether some transport protocol affects the keys such as Http and Mail protocols can do.
030 * <p/>
031 * When copying from this map to a regular Map such as {@link java.util.HashMap} then the original keys are
032 * copied over and you get the old behavior back using a regular Map with case sensitive keys.
033 * <p/>
034 * This map is <b>not</b> designed to be thread safe as concurrent access to it is not supposed to be performed
035 * by the Camel routing engine.
036 *
037 * @version 
038 */
039public class CaseInsensitiveMap extends HashMap<String, Object> {
040    private static final long serialVersionUID = -8538318195477618308L;
041
042    // holds a map of lower case key -> original key
043    private Map<String, String> originalKeys = new HashMap<String, String>();
044    // holds a snapshot view of current entry set
045    private transient Set<Map.Entry<String, Object>> entrySetView;
046
047    public CaseInsensitiveMap() {
048    }
049
050    public CaseInsensitiveMap(Map<? extends String, ?> map) {
051        putAll(map);
052    }
053
054    public CaseInsensitiveMap(int initialCapacity, float loadFactor) {
055        super(initialCapacity, loadFactor);
056        originalKeys = new HashMap<String, String>(initialCapacity, loadFactor);
057    }
058
059    public CaseInsensitiveMap(int initialCapacity) {
060        super(initialCapacity);
061        originalKeys = new HashMap<String, String>(initialCapacity);
062    }
063
064    @Override
065    public Object get(Object key) {
066        String s = assembleKey(key);
067        Object answer = super.get(s);
068        if (answer == null) {
069            // fallback to lookup by original key
070            String originalKey = originalKeys.get(s);
071            answer = super.get(originalKey);
072        }
073        return answer;
074    }
075
076    @Override
077    public synchronized Object put(String key, Object value) {
078        // invalidate views as we mutate
079        entrySetView = null;
080        String s = assembleKey(key);
081        originalKeys.put(s, key);
082        return super.put(s, value);
083    }
084
085    @Override
086    public synchronized void putAll(Map<? extends String, ?> map) {
087        entrySetView = null;
088        if (map instanceof CaseInsensitiveMap) {
089            CaseInsensitiveMap cmap = (CaseInsensitiveMap)map;
090            originalKeys.putAll(cmap.getOriginalKeys());
091            // Calling the super.putAll() is much slower in JDK 1.7
092            for (Map.Entry<String, Object> entry : cmap.superEntrySet()) {
093                super.put(entry.getKey(), entry.getValue());
094            }
095        } else {
096            if (map != null && !map.isEmpty()) {
097                for (Map.Entry<? extends String, ?> entry : map.entrySet()) {
098                    String key = entry.getKey();
099                    Object value = entry.getValue();
100                    String s = assembleKey(key);
101                    originalKeys.put(s, key);
102                    super.put(s, value);
103                }
104            }
105        }
106    }
107    
108    protected synchronized Map<String, String> getOriginalKeys() {
109        return originalKeys;
110    }
111    
112    protected synchronized Set<Map.Entry<String, Object>> superEntrySet() {
113        return super.entrySet();
114    }
115 
116    @Override
117    public synchronized Object remove(Object key) {
118        if (key == null) {
119            return null;
120        }
121
122        // invalidate views as we mutate
123        entrySetView = null;
124        String s = assembleKey(key);
125        originalKeys.remove(s);
126        return super.remove(s);
127    }
128
129    @Override
130    public synchronized void clear() {
131        // invalidate views as we mutate
132        entrySetView = null;
133        originalKeys.clear();
134        super.clear();
135    }
136
137    @Override
138    public boolean containsKey(Object key) {
139        if (key == null) {
140            return false;
141        }
142
143        String s = assembleKey(key);
144        return super.containsKey(s);
145    }
146
147    private static String assembleKey(Object key) {
148        return key.toString().toLowerCase(Locale.ENGLISH);
149    }
150
151    @Override
152    public synchronized Set<Map.Entry<String, Object>> entrySet() {
153        if (entrySetView == null) {
154            // build the key set using the original keys so we retain their case
155            // when for example we copy values to another map
156            entrySetView = new HashSet<Map.Entry<String, Object>>(this.size());
157            for (final Map.Entry<String, Object> entry : super.entrySet()) {
158                Map.Entry<String, Object> view = new Map.Entry<String, Object>() {
159                    public String getKey() {
160                        String s = entry.getKey();
161                        // use the original key so we can preserve it
162                        return originalKeys.get(s);
163                    }
164
165                    public Object getValue() {
166                        return entry.getValue();
167                    }
168
169                    public Object setValue(Object o) {
170                        return entry.setValue(o);
171                    }
172                };
173                entrySetView.add(view);
174            }
175        }
176
177        return entrySetView;
178    }
179}