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 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 != null && !map.isEmpty()) { 089 for (Map.Entry<? extends String, ?> entry : map.entrySet()) { 090 String key = entry.getKey(); 091 Object value = entry.getValue(); 092 String s = assembleKey(key); 093 originalKeys.put(s, key); 094 super.put(s, value); 095 } 096 } 097 } 098 099 @Override 100 public synchronized Object remove(Object key) { 101 if (key == null) { 102 return null; 103 } 104 105 // invalidate views as we mutate 106 entrySetView = null; 107 String s = assembleKey(key); 108 originalKeys.remove(s); 109 return super.remove(s); 110 } 111 112 @Override 113 public synchronized void clear() { 114 // invalidate views as we mutate 115 entrySetView = null; 116 originalKeys.clear(); 117 super.clear(); 118 } 119 120 @Override 121 public boolean containsKey(Object key) { 122 if (key == null) { 123 return false; 124 } 125 126 String s = assembleKey(key); 127 return super.containsKey(s); 128 } 129 130 private static String assembleKey(Object key) { 131 return key.toString().toLowerCase(Locale.ENGLISH); 132 } 133 134 @Override 135 public synchronized Set<Map.Entry<String, Object>> entrySet() { 136 if (entrySetView == null) { 137 // build the key set using the original keys so we retain their case 138 // when for example we copy values to another map 139 entrySetView = new HashSet<Map.Entry<String, Object>>(this.size()); 140 for (final Map.Entry<String, Object> entry : super.entrySet()) { 141 Map.Entry<String, Object> view = new Map.Entry<String, Object>() { 142 public String getKey() { 143 String s = entry.getKey(); 144 // use the original key so we can preserve it 145 return originalKeys.get(s); 146 } 147 148 public Object getValue() { 149 return entry.getValue(); 150 } 151 152 public Object setValue(Object o) { 153 return entry.setValue(o); 154 } 155 }; 156 entrySetView.add(view); 157 } 158 } 159 160 return entrySetView; 161 } 162 }