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}