001 package com.mockrunner.util.common; 002 003 import java.util.Collection; 004 import java.util.HashMap; 005 import java.util.Iterator; 006 import java.util.Map; 007 import java.util.Set; 008 009 /** 010 * Implementation of a <code>Map</code> that recognizes the case of the 011 * keys, if the keys are strings. If <code>isCaseSensitive</code> is 012 * <code>true</code> it behaves exactly like a <code>HashMap</code>. 013 * If <code>isCaseSensitive</code> is <code>false</code> (which is the 014 * default), it considers same strings with different case as equal. 015 * I.e. if you do 016 * <br> 017 * <br> 018 * <code>put("test", "1");</code> 019 * <br> 020 * <code>put("TEST", "2");</code> 021 * <br> 022 * <br> 023 * the second <code>put</code> overwrites the value of the first one, 024 * because the keys are considered to be equal. With 025 * <br> 026 * <br> 027 * <code>get("TesT");</code> 028 * <br> 029 * <br> 030 * you'll get the result <code>"2"</code>. 031 * If you iterate through the keys (using either <code>keySet</code> or 032 * <code>entrySet</code>), you'll get the first added version of the key, 033 * in the above case, you'll get <code>"test"</code>. 034 * It is allowed to use non-strings as keys. In this case the <code>Map</code> 035 * behaves like a usual <code>HashMap</code>. 036 */ 037 public class CaseAwareMap implements Map 038 { 039 private boolean isCaseSensitive; 040 private Map caseInsensitiveMap; 041 private Map actualMap; 042 043 public CaseAwareMap() 044 { 045 this(false); 046 } 047 048 public CaseAwareMap(boolean isCaseSensitive) 049 { 050 this.isCaseSensitive = isCaseSensitive; 051 caseInsensitiveMap = new HashMap(); 052 actualMap = new HashMap(); 053 } 054 055 /** 056 * Returns if keys are case sensitive. Defaults to <code>false</code>. 057 * @return are keys case sensitive 058 */ 059 public boolean isCaseSensitive() 060 { 061 return isCaseSensitive; 062 } 063 064 /** 065 * Sets if keys are case sensitive. 066 * If set to <code>true</code> this implementation behaves like 067 * a <code>HashMap</code>. Please note, that all entries are cleared 068 * when switching case sensitivity. It's not possible to switch 069 * and keep the entries. 070 * @param isCaseSensitive are keys case sensitive 071 */ 072 public void setCaseSensitive(boolean isCaseSensitive) 073 { 074 clear(); 075 this.isCaseSensitive = isCaseSensitive; 076 } 077 078 public void clear() 079 { 080 caseInsensitiveMap.clear(); 081 actualMap.clear(); 082 } 083 084 public boolean containsKey(Object key) 085 { 086 Object compareKey = getCompareKey(key); 087 return getCompareMap().containsKey(compareKey); 088 } 089 090 public boolean containsValue(Object value) 091 { 092 return actualMap.containsValue(value); 093 } 094 095 public Set entrySet() 096 { 097 return actualMap.entrySet(); 098 } 099 100 public Object get(Object key) 101 { 102 Object compareKey = getCompareKey(key); 103 return getCompareMap().get(compareKey); 104 } 105 106 public boolean isEmpty() 107 { 108 return size() <= 0; 109 } 110 111 public Set keySet() 112 { 113 return actualMap.keySet(); 114 } 115 116 public Object put(Object key, Object value) 117 { 118 return doConsistentModify(key, new ConsistentPut(value)); 119 } 120 121 public void putAll(Map map) 122 { 123 Iterator keys = map.keySet().iterator(); 124 while(keys.hasNext()) 125 { 126 Object nextKey = keys.next(); 127 Object nextValue = map.get(nextKey); 128 put(nextKey, nextValue); 129 } 130 } 131 132 public Object remove(Object key) 133 { 134 return doConsistentModify(key, new ConsistentRemove()); 135 } 136 137 public int size() 138 { 139 return actualMap.size(); 140 } 141 142 public Collection values() 143 { 144 return actualMap.values(); 145 } 146 147 private boolean areKeysEquals(Object actualKey, Object compareKey) 148 { 149 if(null == actualKey && null == compareKey) return true; 150 if(null == actualKey) return false; 151 if(null == compareKey) return false; 152 Object actualCompareKey = getCompareKey(actualKey); 153 return compareKey.equals(actualCompareKey); 154 } 155 156 private boolean isStringKey(Object key) 157 { 158 return (null != key) && (key instanceof String); 159 } 160 161 private Object getCompareKey(Object key) 162 { 163 if(isCaseSensitive || !isStringKey(key)) 164 { 165 return key; 166 } 167 return ((String)key).toUpperCase(); 168 } 169 170 private Map getCompareMap() 171 { 172 if(isCaseSensitive) 173 { 174 return actualMap; 175 } 176 return caseInsensitiveMap; 177 } 178 179 private Object doConsistentModify(Object key, ConsistentModify modifier) 180 { 181 Object compareKey = getCompareKey(key); 182 if(!caseInsensitiveMap.containsKey(compareKey)) 183 { 184 return modifier.modify(key, compareKey); 185 } 186 Iterator iterator = actualMap.keySet().iterator(); 187 while(iterator.hasNext()) 188 { 189 Object actualKey = iterator.next(); 190 if(areKeysEquals(actualKey, compareKey)) 191 { 192 return modifier.modify(actualKey, compareKey); 193 } 194 } 195 return null; 196 } 197 198 private interface ConsistentModify 199 { 200 public Object modify(Object key1, Object key2); 201 } 202 203 private class ConsistentRemove implements ConsistentModify 204 { 205 public Object modify(Object key1, Object key2) 206 { 207 actualMap.remove(key1); 208 return caseInsensitiveMap.remove(key2); 209 } 210 } 211 212 private class ConsistentPut implements ConsistentModify 213 { 214 private Object value; 215 216 public ConsistentPut(Object value) 217 { 218 this.value = value; 219 } 220 221 public Object modify(Object key1, Object key2) 222 { 223 actualMap.put(key1, value); 224 return caseInsensitiveMap.put(key2, value); 225 } 226 } 227 }