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.Locale;
022 import java.util.Map;
023 import 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 */
039 public 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 if (key.startsWith("Camel")) {
082 // use intern String for headers which is Camel* headers
083 // this reduces memory allocations needed for those common headers
084 originalKeys.put(s, key.intern());
085 } else {
086 originalKeys.put(s, key);
087 }
088 return super.put(s, value);
089 }
090
091 @Override
092 public synchronized void putAll(Map<? extends String, ?> map) {
093 entrySetView = null;
094 if (map != null && !map.isEmpty()) {
095 for (Map.Entry<? extends String, ?> entry : map.entrySet()) {
096 String key = entry.getKey();
097 Object value = entry.getValue();
098 String s = assembleKey(key);
099 if (key.startsWith("Camel")) {
100 // use intern String for headers which is Camel* headers
101 // this reduces memory allocations needed for those common headers
102 originalKeys.put(s, key.intern());
103 } else {
104 originalKeys.put(s, key);
105 }
106 super.put(s, value);
107 }
108 }
109 }
110
111 @Override
112 public synchronized Object remove(Object key) {
113 if (key == null) {
114 return null;
115 }
116
117 // invalidate views as we mutate
118 entrySetView = null;
119 String s = assembleKey(key);
120 originalKeys.remove(s);
121 return super.remove(s);
122 }
123
124 @Override
125 public synchronized void clear() {
126 // invalidate views as we mutate
127 entrySetView = null;
128 originalKeys.clear();
129 super.clear();
130 }
131
132 @Override
133 public boolean containsKey(Object key) {
134 if (key == null) {
135 return false;
136 }
137
138 String s = assembleKey(key);
139 return super.containsKey(s);
140 }
141
142 private static String assembleKey(Object key) {
143 String s = key.toString().toLowerCase(Locale.ENGLISH);
144 if (s.startsWith("camel")) {
145 // use intern String for headers which is Camel* headers
146 // this reduces memory allocations needed for those common headers
147 s = s.intern();
148 }
149 return s;
150 }
151
152 @Override
153 public synchronized Set<Map.Entry<String, Object>> entrySet() {
154 if (entrySetView == null) {
155 // build the key set using the original keys so we retain their case
156 // when for example we copy values to another map
157 entrySetView = new HashSet<Map.Entry<String, Object>>(this.size());
158 for (final Map.Entry<String, Object> entry : super.entrySet()) {
159 Map.Entry<String, Object> view = new Map.Entry<String, Object>() {
160 public String getKey() {
161 String s = entry.getKey();
162 // use the original key so we can preserve it
163 return originalKeys.get(s);
164 }
165
166 public Object getValue() {
167 return entry.getValue();
168 }
169
170 public Object setValue(Object o) {
171 return entry.setValue(o);
172 }
173 };
174 entrySetView.add(view);
175 }
176 }
177
178 return entrySetView;
179 }
180 }