001: /*
002: * Copyright 2003-2004 The Apache Software Foundation
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.apache.commons.collections.map;
017:
018: import java.io.Serializable;
019: import java.util.AbstractSet;
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.Iterator;
023: import java.util.Map;
024: import java.util.NoSuchElementException;
025: import java.util.Set;
026:
027: import org.apache.commons.collections.BoundedMap;
028: import org.apache.commons.collections.KeyValue;
029: import org.apache.commons.collections.MapIterator;
030: import org.apache.commons.collections.OrderedMap;
031: import org.apache.commons.collections.OrderedMapIterator;
032: import org.apache.commons.collections.ResettableIterator;
033: import org.apache.commons.collections.iterators.SingletonIterator;
034: import org.apache.commons.collections.keyvalue.TiedMapEntry;
035:
036: /**
037: * A <code>Map</code> implementation that holds a single item and is fixed size.
038: * <p>
039: * The single key/value pair is specified at creation.
040: * The map is fixed size so any action that would change the size is disallowed.
041: * However, the <code>put</code> or <code>setValue</code> methods can <i>change</i>
042: * the value associated with the key.
043: * <p>
044: * If trying to remove or clear the map, an UnsupportedOperationException is thrown.
045: * If trying to put a new mapping into the map, an IllegalArgumentException is thrown.
046: * The put method will only suceed if the key specified is the same as the
047: * singleton key.
048: * <p>
049: * The key and value can be obtained by:
050: * <ul>
051: * <li>normal Map methods and views
052: * <li>the <code>MapIterator</code>, see {@link #mapIterator()}
053: * <li>the <code>KeyValue</code> interface (just cast - no object creation)
054: * </ul>
055: *
056: * @since Commons Collections 3.1
057: * @version $Revision: 155406 $ $Date: 2005-02-26 12:55:26 +0000 (Sat, 26 Feb 2005) $
058: *
059: * @author Stephen Colebourne
060: */
061: public class SingletonMap implements OrderedMap, BoundedMap, KeyValue,
062: Serializable, Cloneable {
063:
064: /** Serialization version */
065: private static final long serialVersionUID = -8931271118676803261L;
066:
067: /** Singleton key */
068: private final Object key;
069: /** Singleton value */
070: private Object value;
071:
072: /**
073: * Constructor that creates a map of <code>null</code> to <code>null</code>.
074: */
075: public SingletonMap() {
076: super ();
077: this .key = null;
078: }
079:
080: /**
081: * Constructor specifying the key and value.
082: *
083: * @param key the key to use
084: * @param value the value to use
085: */
086: public SingletonMap(Object key, Object value) {
087: super ();
088: this .key = key;
089: this .value = value;
090: }
091:
092: /**
093: * Constructor specifying the key and value as a <code>KeyValue</code>.
094: *
095: * @param keyValue the key value pair to use
096: */
097: public SingletonMap(KeyValue keyValue) {
098: super ();
099: this .key = keyValue.getKey();
100: this .value = keyValue.getValue();
101: }
102:
103: /**
104: * Constructor specifying the key and value as a <code>MapEntry</code>.
105: *
106: * @param mapEntry the mapEntry to use
107: */
108: public SingletonMap(Map.Entry mapEntry) {
109: super ();
110: this .key = mapEntry.getKey();
111: this .value = mapEntry.getValue();
112: }
113:
114: /**
115: * Constructor copying elements from another map.
116: *
117: * @param map the map to copy, must be size 1
118: * @throws NullPointerException if the map is null
119: * @throws IllegalArgumentException if the size is not 1
120: */
121: public SingletonMap(Map map) {
122: super ();
123: if (map.size() != 1) {
124: throw new IllegalArgumentException("The map size must be 1");
125: }
126: Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();
127: this .key = entry.getKey();
128: this .value = entry.getValue();
129: }
130:
131: // KeyValue
132: //-----------------------------------------------------------------------
133: /**
134: * Gets the key.
135: *
136: * @return the key
137: */
138: public Object getKey() {
139: return key;
140: }
141:
142: /**
143: * Gets the value.
144: *
145: * @return the value
146: */
147: public Object getValue() {
148: return value;
149: }
150:
151: /**
152: * Sets the value.
153: *
154: * @param value the new value to set
155: * @return the old value
156: */
157: public Object setValue(Object value) {
158: Object old = this .value;
159: this .value = value;
160: return old;
161: }
162:
163: // BoundedMap
164: //-----------------------------------------------------------------------
165: /**
166: * Is the map currently full, always true.
167: *
168: * @return true always
169: */
170: public boolean isFull() {
171: return true;
172: }
173:
174: /**
175: * Gets the maximum size of the map, always 1.
176: *
177: * @return 1 always
178: */
179: public int maxSize() {
180: return 1;
181: }
182:
183: // Map
184: //-----------------------------------------------------------------------
185: /**
186: * Gets the value mapped to the key specified.
187: *
188: * @param key the key
189: * @return the mapped value, null if no match
190: */
191: public Object get(Object key) {
192: if (isEqualKey(key)) {
193: return value;
194: }
195: return null;
196: }
197:
198: /**
199: * Gets the size of the map, always 1.
200: *
201: * @return the size of 1
202: */
203: public int size() {
204: return 1;
205: }
206:
207: /**
208: * Checks whether the map is currently empty, which it never is.
209: *
210: * @return false always
211: */
212: public boolean isEmpty() {
213: return false;
214: }
215:
216: //-----------------------------------------------------------------------
217: /**
218: * Checks whether the map contains the specified key.
219: *
220: * @param key the key to search for
221: * @return true if the map contains the key
222: */
223: public boolean containsKey(Object key) {
224: return (isEqualKey(key));
225: }
226:
227: /**
228: * Checks whether the map contains the specified value.
229: *
230: * @param value the value to search for
231: * @return true if the map contains the key
232: */
233: public boolean containsValue(Object value) {
234: return (isEqualValue(value));
235: }
236:
237: //-----------------------------------------------------------------------
238: /**
239: * Puts a key-value mapping into this map where the key must match the existing key.
240: * <p>
241: * An IllegalArgumentException is thrown if the key does not match as the map
242: * is fixed size.
243: *
244: * @param key the key to set, must be the key of the map
245: * @param value the value to set
246: * @return the value previously mapped to this key, null if none
247: * @throws IllegalArgumentException if the key does not match
248: */
249: public Object put(Object key, Object value) {
250: if (isEqualKey(key)) {
251: return setValue(value);
252: }
253: throw new IllegalArgumentException(
254: "Cannot put new key/value pair - Map is fixed size singleton");
255: }
256:
257: /**
258: * Puts the values from the specified map into this map.
259: * <p>
260: * The map must be of size 0 or size 1.
261: * If it is size 1, the key must match the key of this map otherwise an
262: * IllegalArgumentException is thrown.
263: *
264: * @param map the map to add, must be size 0 or 1, and the key must match
265: * @throws NullPointerException if the map is null
266: * @throws IllegalArgumentException if the key does not match
267: */
268: public void putAll(Map map) {
269: switch (map.size()) {
270: case 0:
271: return;
272:
273: case 1:
274: Map.Entry entry = (Map.Entry) map.entrySet().iterator()
275: .next();
276: put(entry.getKey(), entry.getValue());
277: return;
278:
279: default:
280: throw new IllegalArgumentException(
281: "The map size must be 0 or 1");
282: }
283: }
284:
285: /**
286: * Unsupported operation.
287: *
288: * @param key the mapping to remove
289: * @return the value mapped to the removed key, null if key not in map
290: * @throws UnsupportedOperationException always
291: */
292: public Object remove(Object key) {
293: throw new UnsupportedOperationException();
294: }
295:
296: /**
297: * Unsupported operation.
298: */
299: public void clear() {
300: throw new UnsupportedOperationException();
301: }
302:
303: //-----------------------------------------------------------------------
304: /**
305: * Gets the entrySet view of the map.
306: * Changes made via <code>setValue</code> affect this map.
307: * To simply iterate through the entries, use {@link #mapIterator()}.
308: *
309: * @return the entrySet view
310: */
311: public Set entrySet() {
312: Map.Entry entry = new TiedMapEntry(this , getKey());
313: return Collections.singleton(entry);
314: }
315:
316: /**
317: * Gets the unmodifiable keySet view of the map.
318: * Changes made to the view affect this map.
319: * To simply iterate through the keys, use {@link #mapIterator()}.
320: *
321: * @return the keySet view
322: */
323: public Set keySet() {
324: return Collections.singleton(key);
325: }
326:
327: /**
328: * Gets the unmodifiable values view of the map.
329: * Changes made to the view affect this map.
330: * To simply iterate through the values, use {@link #mapIterator()}.
331: *
332: * @return the values view
333: */
334: public Collection values() {
335: return new SingletonValues(this );
336: }
337:
338: /**
339: * Gets an iterator over the map.
340: * Changes made to the iterator using <code>setValue</code> affect this map.
341: * The <code>remove</code> method is unsupported.
342: * <p>
343: * A MapIterator returns the keys in the map. It also provides convenient
344: * methods to get the key and value, and set the value.
345: * It avoids the need to create an entrySet/keySet/values object.
346: * It also avoids creating the Map Entry object.
347: *
348: * @return the map iterator
349: */
350: public MapIterator mapIterator() {
351: return new SingletonMapIterator(this );
352: }
353:
354: // OrderedMap
355: //-----------------------------------------------------------------------
356: /**
357: * Obtains an <code>OrderedMapIterator</code> over the map.
358: * <p>
359: * A ordered map iterator is an efficient way of iterating over maps
360: * in both directions.
361: *
362: * @return an ordered map iterator
363: */
364: public OrderedMapIterator orderedMapIterator() {
365: return new SingletonMapIterator(this );
366: }
367:
368: /**
369: * Gets the first (and only) key in the map.
370: *
371: * @return the key
372: */
373: public Object firstKey() {
374: return getKey();
375: }
376:
377: /**
378: * Gets the last (and only) key in the map.
379: *
380: * @return the key
381: */
382: public Object lastKey() {
383: return getKey();
384: }
385:
386: /**
387: * Gets the next key after the key specified, always null.
388: *
389: * @param key the next key
390: * @return null always
391: */
392: public Object nextKey(Object key) {
393: return null;
394: }
395:
396: /**
397: * Gets the previous key before the key specified, always null.
398: *
399: * @param key the next key
400: * @return null always
401: */
402: public Object previousKey(Object key) {
403: return null;
404: }
405:
406: //-----------------------------------------------------------------------
407: /**
408: * Compares the specified key to the stored key.
409: *
410: * @param key the key to compare
411: * @return true if equal
412: */
413: protected boolean isEqualKey(Object key) {
414: return (key == null ? getKey() == null : key.equals(getKey()));
415: }
416:
417: /**
418: * Compares the specified value to the stored value.
419: *
420: * @param value the value to compare
421: * @return true if equal
422: */
423: protected boolean isEqualValue(Object value) {
424: return (value == null ? getValue() == null : value
425: .equals(getValue()));
426: }
427:
428: //-----------------------------------------------------------------------
429: /**
430: * SingletonMapIterator.
431: */
432: static class SingletonMapIterator implements OrderedMapIterator,
433: ResettableIterator {
434: private final SingletonMap parent;
435: private boolean hasNext = true;
436: private boolean canGetSet = false;
437:
438: SingletonMapIterator(SingletonMap parent) {
439: super ();
440: this .parent = parent;
441: }
442:
443: public boolean hasNext() {
444: return hasNext;
445: }
446:
447: public Object next() {
448: if (hasNext == false) {
449: throw new NoSuchElementException(
450: AbstractHashedMap.NO_NEXT_ENTRY);
451: }
452: hasNext = false;
453: canGetSet = true;
454: return parent.getKey();
455: }
456:
457: public boolean hasPrevious() {
458: return (hasNext == false);
459: }
460:
461: public Object previous() {
462: if (hasNext == true) {
463: throw new NoSuchElementException(
464: AbstractHashedMap.NO_PREVIOUS_ENTRY);
465: }
466: hasNext = true;
467: return parent.getKey();
468: }
469:
470: public void remove() {
471: throw new UnsupportedOperationException();
472: }
473:
474: public Object getKey() {
475: if (canGetSet == false) {
476: throw new IllegalStateException(
477: AbstractHashedMap.GETKEY_INVALID);
478: }
479: return parent.getKey();
480: }
481:
482: public Object getValue() {
483: if (canGetSet == false) {
484: throw new IllegalStateException(
485: AbstractHashedMap.GETVALUE_INVALID);
486: }
487: return parent.getValue();
488: }
489:
490: public Object setValue(Object value) {
491: if (canGetSet == false) {
492: throw new IllegalStateException(
493: AbstractHashedMap.SETVALUE_INVALID);
494: }
495: return parent.setValue(value);
496: }
497:
498: public void reset() {
499: hasNext = true;
500: }
501:
502: public String toString() {
503: if (hasNext) {
504: return "Iterator[]";
505: } else {
506: return "Iterator[" + getKey() + "=" + getValue() + "]";
507: }
508: }
509: }
510:
511: /**
512: * Values implementation for the SingletonMap.
513: * This class is needed as values is a view that must update as the map updates.
514: */
515: static class SingletonValues extends AbstractSet implements
516: Serializable {
517: private static final long serialVersionUID = -3689524741863047872L;
518: private final SingletonMap parent;
519:
520: SingletonValues(SingletonMap parent) {
521: super ();
522: this .parent = parent;
523: }
524:
525: public int size() {
526: return 1;
527: }
528:
529: public boolean isEmpty() {
530: return false;
531: }
532:
533: public boolean contains(Object object) {
534: return parent.containsValue(object);
535: }
536:
537: public void clear() {
538: throw new UnsupportedOperationException();
539: }
540:
541: public Iterator iterator() {
542: return new SingletonIterator(parent.getValue(), false);
543: }
544: }
545:
546: //-----------------------------------------------------------------------
547: /**
548: * Clones the map without cloning the key or value.
549: *
550: * @return a shallow clone
551: */
552: public Object clone() {
553: try {
554: SingletonMap cloned = (SingletonMap) super .clone();
555: return cloned;
556: } catch (CloneNotSupportedException ex) {
557: throw new InternalError();
558: }
559: }
560:
561: /**
562: * Compares this map with another.
563: *
564: * @param obj the object to compare to
565: * @return true if equal
566: */
567: public boolean equals(Object obj) {
568: if (obj == this ) {
569: return true;
570: }
571: if (obj instanceof Map == false) {
572: return false;
573: }
574: Map other = (Map) obj;
575: if (other.size() != 1) {
576: return false;
577: }
578: Map.Entry entry = (Map.Entry) other.entrySet().iterator()
579: .next();
580: return isEqualKey(entry.getKey())
581: && isEqualValue(entry.getValue());
582: }
583:
584: /**
585: * Gets the standard Map hashCode.
586: *
587: * @return the hash code defined in the Map interface
588: */
589: public int hashCode() {
590: return (getKey() == null ? 0 : getKey().hashCode())
591: ^ (getValue() == null ? 0 : getValue().hashCode());
592: }
593:
594: /**
595: * Gets the map as a String.
596: *
597: * @return a string version of the map
598: */
599: public String toString() {
600: return new StringBuffer(128).append('{').append(
601: (getKey() == this ? "(this Map)" : getKey())).append(
602: '=').append(
603: (getValue() == this ? "(this Map)" : getValue()))
604: .append('}').toString();
605: }
606:
607: }
|