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 }