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>.<br>
036: * Note: This class is similar to a <code>TreeMap(String.CASE_INSENSITIVE_ORDER)</code>
037: * except that non-strings do not throw a <code>ClassCastException</code>
038: * and that keys are not sorted.
039: */
040: public class CaseAwareMap implements Map {
041: private boolean isCaseSensitive;
042: private Map caseInsensitiveMap;
043: private Map actualMap;
044:
045: public CaseAwareMap() {
046: this (false);
047: }
048:
049: public CaseAwareMap(boolean isCaseSensitive) {
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: return isCaseSensitive;
061: }
062:
063: /**
064: * Sets if keys are case sensitive.
065: * If set to <code>true</code> this implementation behaves like
066: * a <code>HashMap</code>. Please note, that all entries are cleared
067: * when switching case sensitivity. It's not possible to switch
068: * and keep the entries.
069: * @param isCaseSensitive are keys case sensitive
070: */
071: public void setCaseSensitive(boolean isCaseSensitive) {
072: clear();
073: this .isCaseSensitive = isCaseSensitive;
074: }
075:
076: public void clear() {
077: caseInsensitiveMap.clear();
078: actualMap.clear();
079: }
080:
081: public boolean containsKey(Object key) {
082: Object compareKey = getCompareKey(key);
083: return getCompareMap().containsKey(compareKey);
084: }
085:
086: public boolean containsValue(Object value) {
087: return actualMap.containsValue(value);
088: }
089:
090: public Set entrySet() {
091: return actualMap.entrySet();
092: }
093:
094: public Object get(Object key) {
095: Object compareKey = getCompareKey(key);
096: return getCompareMap().get(compareKey);
097: }
098:
099: public boolean isEmpty() {
100: return size() <= 0;
101: }
102:
103: public Set keySet() {
104: return actualMap.keySet();
105: }
106:
107: public Object put(Object key, Object value) {
108: return doConsistentModify(key, new ConsistentPut(value));
109: }
110:
111: public void putAll(Map map) {
112: Iterator keys = map.keySet().iterator();
113: while (keys.hasNext()) {
114: Object nextKey = keys.next();
115: Object nextValue = map.get(nextKey);
116: put(nextKey, nextValue);
117: }
118: }
119:
120: public Object remove(Object key) {
121: return doConsistentModify(key, new ConsistentRemove());
122: }
123:
124: public int size() {
125: return actualMap.size();
126: }
127:
128: public Collection values() {
129: return actualMap.values();
130: }
131:
132: private boolean areKeysEquals(Object actualKey, Object compareKey) {
133: if (null == actualKey && null == compareKey)
134: return true;
135: if (null == actualKey)
136: return false;
137: if (null == compareKey)
138: return false;
139: Object actualCompareKey = getCompareKey(actualKey);
140: return compareKey.equals(actualCompareKey);
141: }
142:
143: private boolean isStringKey(Object key) {
144: return (null != key) && (key instanceof String);
145: }
146:
147: private Object getCompareKey(Object key) {
148: if (isCaseSensitive || !isStringKey(key)) {
149: return key;
150: }
151: return ((String) key).toUpperCase();
152: }
153:
154: private Map getCompareMap() {
155: if (isCaseSensitive) {
156: return actualMap;
157: }
158: return caseInsensitiveMap;
159: }
160:
161: private Object doConsistentModify(Object key,
162: ConsistentModify modifier) {
163: Object compareKey = getCompareKey(key);
164: if (!caseInsensitiveMap.containsKey(compareKey)) {
165: return modifier.modify(key, compareKey);
166: }
167: Iterator iterator = actualMap.keySet().iterator();
168: while (iterator.hasNext()) {
169: Object actualKey = iterator.next();
170: if (areKeysEquals(actualKey, compareKey)) {
171: return modifier.modify(actualKey, compareKey);
172: }
173: }
174: return null;
175: }
176:
177: private interface ConsistentModify {
178: public Object modify(Object key1, Object key2);
179: }
180:
181: private class ConsistentRemove implements ConsistentModify {
182: public Object modify(Object key1, Object key2) {
183: actualMap.remove(key1);
184: return caseInsensitiveMap.remove(key2);
185: }
186: }
187:
188: private class ConsistentPut implements ConsistentModify {
189: private Object value;
190:
191: public ConsistentPut(Object value) {
192: this .value = value;
193: }
194:
195: public Object modify(Object key1, Object key2) {
196: actualMap.put(key1, value);
197: return caseInsensitiveMap.put(key2, value);
198: }
199: }
200: }
|