001: /*
002: * Copyright 2004-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.springframework.binding.collection;
017:
018: import java.util.Collection;
019: import java.util.Iterator;
020: import java.util.Map;
021: import java.util.NoSuchElementException;
022: import java.util.Set;
023:
024: /**
025: * Base class for map adapters whose keys are String values. Concrete
026: * classes need only implement the abstract hook methods defined by this class.
027: *
028: * @author Keith Donald
029: */
030: public abstract class StringKeyedMapAdapter implements Map {
031:
032: private Set keySet;
033:
034: private Collection values;
035:
036: private Set entrySet;
037:
038: // implementing Map
039:
040: public void clear() {
041: for (Iterator it = getAttributeNames(); it.hasNext();) {
042: removeAttribute((String) it.next());
043: }
044: }
045:
046: public boolean containsKey(Object key) {
047: return getAttribute(key.toString()) != null;
048: }
049:
050: public boolean containsValue(Object value) {
051: if (value == null) {
052: return false;
053: }
054: for (Iterator it = getAttributeNames(); it.hasNext();) {
055: Object aValue = getAttribute((String) it.next());
056: if (value.equals(aValue)) {
057: return true;
058: }
059: }
060: return false;
061: }
062:
063: public Set entrySet() {
064: return (entrySet != null) ? entrySet
065: : (entrySet = new EntrySet());
066: }
067:
068: public Object get(Object key) {
069: return getAttribute(key.toString());
070: }
071:
072: public boolean isEmpty() {
073: return !getAttributeNames().hasNext();
074: }
075:
076: public Set keySet() {
077: return (keySet != null) ? keySet : (keySet = new KeySet());
078: }
079:
080: public Object put(Object key, Object value) {
081: String stringKey = String.valueOf(key);
082: Object previousValue = getAttribute(stringKey);
083: setAttribute(stringKey, value);
084: return previousValue;
085: }
086:
087: public void putAll(Map map) {
088: for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
089: Entry entry = (Entry) it.next();
090: setAttribute(entry.getKey().toString(), entry.getValue());
091: }
092: }
093:
094: public Object remove(Object key) {
095: String stringKey = key.toString();
096: Object retval = getAttribute(stringKey);
097: removeAttribute(stringKey);
098: return retval;
099: }
100:
101: public int size() {
102: int size = 0;
103: for (Iterator it = getAttributeNames(); it.hasNext();) {
104: size++;
105: it.next();
106: }
107: return size;
108: }
109:
110: public Collection values() {
111: return (values != null) ? values : (values = new Values());
112: }
113:
114: // hook methods
115:
116: /**
117: * Hook method that needs to be implemented by concrete subclasses.
118: * Gets a value associated with a key.
119: * @param key the key to lookup
120: * @return the associated value, or null if none
121: */
122: protected abstract Object getAttribute(String key);
123:
124: /**
125: * Hook method that needs to be implemented by concrete subclasses.
126: * Puts a key-value pair in the map, overwriting any possible earlier
127: * value associated with the same key.
128: * @param key the key to associate the value with
129: * @param value the value to associate with the key
130: */
131: protected abstract void setAttribute(String key, Object value);
132:
133: /**
134: * Hook method that needs to be implemented by concrete subclasses.
135: * Removes a key and its associated value from the map.
136: * @param key the key to remove
137: */
138: protected abstract void removeAttribute(String key);
139:
140: /**
141: * Hook method that needs to be implemented by concrete subclasses.
142: * Returns an enumeration listing all keys known to the map.
143: * @return the key enumeration
144: */
145: protected abstract Iterator getAttributeNames();
146:
147: // internal helper classes
148:
149: private abstract class AbstractSet extends java.util.AbstractSet {
150: public boolean isEmpty() {
151: return StringKeyedMapAdapter.this .isEmpty();
152: }
153:
154: public int size() {
155: return StringKeyedMapAdapter.this .size();
156: }
157:
158: public void clear() {
159: StringKeyedMapAdapter.this .clear();
160: }
161: }
162:
163: private class KeySet extends AbstractSet {
164: public Iterator iterator() {
165: return new KeyIterator();
166: }
167:
168: public boolean contains(Object o) {
169: return StringKeyedMapAdapter.this .containsKey(o);
170: }
171:
172: public boolean remove(Object o) {
173: return StringKeyedMapAdapter.this .remove(o) != null;
174: }
175: }
176:
177: private class KeyIterator implements Iterator {
178: protected final Iterator it = getAttributeNames();
179:
180: protected Object currentKey;
181:
182: public void remove() {
183: if (currentKey == null) {
184: throw new NoSuchElementException(
185: "You must call next() at least once");
186: }
187: StringKeyedMapAdapter.this .remove(currentKey);
188: }
189:
190: public boolean hasNext() {
191: return it.hasNext();
192: }
193:
194: public Object next() {
195: return currentKey = it.next();
196: }
197: }
198:
199: private class Values extends AbstractSet {
200: public Iterator iterator() {
201: return new ValuesIterator();
202: }
203:
204: public boolean contains(Object o) {
205: return StringKeyedMapAdapter.this .containsValue(o);
206: }
207:
208: public boolean remove(Object o) {
209: if (o == null) {
210: return false;
211: }
212: for (Iterator it = iterator(); it.hasNext();) {
213: if (o.equals(it.next())) {
214: it.remove();
215: return true;
216: }
217: }
218: return false;
219: }
220: }
221:
222: private class ValuesIterator extends KeyIterator {
223: public Object next() {
224: super .next();
225: return StringKeyedMapAdapter.this .get(currentKey);
226: }
227: }
228:
229: private class EntrySet extends AbstractSet {
230: public Iterator iterator() {
231: return new EntryIterator();
232: }
233:
234: public boolean contains(Object o) {
235: if (!(o instanceof Entry)) {
236: return false;
237: }
238: Entry entry = (Entry) o;
239: Object key = entry.getKey();
240: Object value = entry.getValue();
241: if (key == null || value == null) {
242: return false;
243: }
244: return value.equals(StringKeyedMapAdapter.this .get(key));
245: }
246:
247: public boolean remove(Object o) {
248: if (!(o instanceof Entry)) {
249: return false;
250: }
251: Entry entry = (Entry) o;
252: Object key = entry.getKey();
253: Object value = entry.getValue();
254: if (key == null
255: || value == null
256: || !value.equals(StringKeyedMapAdapter.this
257: .get(key))) {
258: return false;
259: }
260: return StringKeyedMapAdapter.this .remove(((Entry) o)
261: .getKey()) != null;
262: }
263: }
264:
265: private class EntryIterator extends KeyIterator {
266: public Object next() {
267: super .next();
268: return new EntrySetEntry(currentKey);
269: }
270: }
271:
272: private class EntrySetEntry implements Entry {
273: private final Object currentKey;
274:
275: public EntrySetEntry(Object currentKey) {
276: this .currentKey = currentKey;
277: }
278:
279: public Object getKey() {
280: return currentKey;
281: }
282:
283: public Object getValue() {
284: return StringKeyedMapAdapter.this .get(currentKey);
285: }
286:
287: public Object setValue(Object value) {
288: return StringKeyedMapAdapter.this.put(currentKey, value);
289: }
290: }
291: }
|