View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections.map;
18  
19  import java.io.Serializable;
20  import java.util.AbstractSet;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.NoSuchElementException;
26  import java.util.Set;
27  
28  import org.apache.commons.collections.BoundedMap;
29  import org.apache.commons.collections.KeyValue;
30  import org.apache.commons.collections.OrderedMap;
31  import org.apache.commons.collections.OrderedMapIterator;
32  import org.apache.commons.collections.ResettableIterator;
33  import org.apache.commons.collections.iterators.SingletonIterator;
34  import org.apache.commons.collections.keyvalue.TiedMapEntry;
35  
36  /**
37   * A <code>Map</code> implementation that holds a single item and is fixed size.
38   * <p>
39   * The single key/value pair is specified at creation.
40   * The map is fixed size so any action that would change the size is disallowed.
41   * However, the <code>put</code> or <code>setValue</code> methods can <i>change</i>
42   * the value associated with the key.
43   * <p>
44   * If trying to remove or clear the map, an UnsupportedOperationException is thrown.
45   * If trying to put a new mapping into the map, an  IllegalArgumentException is thrown.
46   * The put method will only suceed if the key specified is the same as the 
47   * singleton key.
48   * <p>
49   * The key and value can be obtained by:
50   * <ul>
51   * <li>normal Map methods and views
52   * <li>the <code>MapIterator</code>, see {@link #mapIterator()}
53   * <li>the <code>KeyValue</code> interface (just cast - no object creation)
54   * </ul>
55   *
56   * @since 3.1
57   * @version $Id: SingletonMap.java 1429905 2013-01-07 17:15:14Z ggregory $
58   */
59  public class SingletonMap<K, V>
60          implements OrderedMap<K, V>, BoundedMap<K, V>, KeyValue<K, V>, Serializable, Cloneable {
61  
62      /** Serialization version */
63      private static final long serialVersionUID = -8931271118676803261L;
64  
65      /** Singleton key */
66      private final K key;
67      /** Singleton value */
68      private V value;
69  
70      /**
71       * Constructor that creates a map of <code>null</code> to <code>null</code>.
72       */
73      public SingletonMap() {
74          super();
75          this.key = null;
76      }
77  
78      /**
79       * Constructor specifying the key and value.
80       *
81       * @param key  the key to use
82       * @param value  the value to use
83       */
84      public SingletonMap(final K key, final V value) {
85          super();
86          this.key = key;
87          this.value = value;
88      }
89  
90      /**
91       * Constructor specifying the key and value as a <code>KeyValue</code>.
92       *
93       * @param keyValue  the key value pair to use
94       */
95      public SingletonMap(final KeyValue<K, V> keyValue) {
96          super();
97          this.key = keyValue.getKey();
98          this.value = keyValue.getValue();
99      }
100 
101     /**
102      * Constructor specifying the key and value as a <code>MapEntry</code>.
103      *
104      * @param mapEntry  the mapEntry to use
105      */
106     public SingletonMap(final Map.Entry<K, V> mapEntry) {
107         super();
108         this.key = mapEntry.getKey();
109         this.value = mapEntry.getValue();
110     }
111 
112     /**
113      * Constructor copying elements from another map.
114      *
115      * @param map  the map to copy, must be size 1
116      * @throws NullPointerException if the map is null
117      * @throws IllegalArgumentException if the size is not 1
118      */
119     public SingletonMap(final Map<K, V> map) {
120         super();
121         if (map.size() != 1) {
122             throw new IllegalArgumentException("The map size must be 1");
123         }
124         final Map.Entry<K, V> entry = map.entrySet().iterator().next();
125         this.key = entry.getKey();
126         this.value = entry.getValue();
127     }
128 
129     // KeyValue
130     //-----------------------------------------------------------------------
131     /**
132      * Gets the key.
133      *
134      * @return the key 
135      */
136     public K getKey() {
137         return key;
138     }
139 
140     /**
141      * Gets the value.
142      *
143      * @return the value
144      */
145     public V getValue() {
146         return value;
147     }
148 
149     /**
150      * Sets the value.
151      *
152      * @param value  the new value to set
153      * @return the old value
154      */
155     public V setValue(final V value) {
156         final V old = this.value;
157         this.value = value;
158         return old;
159     }
160 
161     // BoundedMap
162     //-----------------------------------------------------------------------
163     /**
164      * Is the map currently full, always true.
165      *
166      * @return true always
167      */
168     public boolean isFull() {
169         return true;
170     }
171 
172     /**
173      * Gets the maximum size of the map, always 1.
174      * 
175      * @return 1 always
176      */
177     public int maxSize() {
178         return 1;
179     }
180 
181     // Map
182     //-----------------------------------------------------------------------
183     /**
184      * Gets the value mapped to the key specified.
185      * 
186      * @param key  the key
187      * @return the mapped value, null if no match
188      */
189     public V get(final Object key) {
190         if (isEqualKey(key)) {
191             return value;
192         }
193         return null;
194     }
195 
196     /**
197      * Gets the size of the map, always 1.
198      * 
199      * @return the size of 1
200      */
201     public int size() {
202         return 1;
203     }
204 
205     /**
206      * Checks whether the map is currently empty, which it never is.
207      * 
208      * @return false always
209      */
210     public boolean isEmpty() {
211         return false;
212     }
213 
214     //-----------------------------------------------------------------------
215     /**
216      * Checks whether the map contains the specified key.
217      * 
218      * @param key  the key to search for
219      * @return true if the map contains the key
220      */
221     public boolean containsKey(final Object key) {
222         return isEqualKey(key);
223     }
224 
225     /**
226      * Checks whether the map contains the specified value.
227      * 
228      * @param value  the value to search for
229      * @return true if the map contains the key
230      */
231     public boolean containsValue(final Object value) {
232         return isEqualValue(value);
233     }
234 
235     //-----------------------------------------------------------------------
236     /**
237      * Puts a key-value mapping into this map where the key must match the existing key.
238      * <p>
239      * An IllegalArgumentException is thrown if the key does not match as the map
240      * is fixed size.
241      * 
242      * @param key  the key to set, must be the key of the map
243      * @param value  the value to set
244      * @return the value previously mapped to this key, null if none
245      * @throws IllegalArgumentException if the key does not match
246      */
247     public V put(final K key, final V value) {
248         if (isEqualKey(key)) {
249             return setValue(value);
250         }
251         throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size singleton");
252     }
253 
254     /**
255      * Puts the values from the specified map into this map.
256      * <p>
257      * The map must be of size 0 or size 1.
258      * If it is size 1, the key must match the key of this map otherwise an
259      * IllegalArgumentException is thrown.
260      * 
261      * @param map  the map to add, must be size 0 or 1, and the key must match
262      * @throws NullPointerException if the map is null
263      * @throws IllegalArgumentException if the key does not match
264      */
265     public void putAll(final Map<? extends K, ? extends V> map) {
266         switch (map.size()) {
267             case 0:
268                 return;
269 
270             case 1:
271                 final Map.Entry<? extends K, ? extends V> entry = map.entrySet().iterator().next();
272                 put(entry.getKey(), entry.getValue());
273                 return;
274 
275             default:
276                 throw new IllegalArgumentException("The map size must be 0 or 1");
277         }
278     }
279     
280     /**
281      * Unsupported operation.
282      * 
283      * @param key  the mapping to remove
284      * @return the value mapped to the removed key, null if key not in map
285      * @throws UnsupportedOperationException always
286      */
287     public V remove(final Object key) {
288         throw new UnsupportedOperationException();
289     }
290 
291     /**
292      * Unsupported operation.
293      */
294     public void clear() {
295         throw new UnsupportedOperationException();
296     }
297 
298     //-----------------------------------------------------------------------
299     /**
300      * Gets the entrySet view of the map.
301      * Changes made via <code>setValue</code> affect this map.
302      * To simply iterate through the entries, use {@link #mapIterator()}.
303      * 
304      * @return the entrySet view
305      */
306     public Set<Map.Entry<K, V>> entrySet() {
307         final Map.Entry<K, V> entry = new TiedMapEntry<K, V>(this, getKey());
308         return Collections.singleton(entry);
309     }
310     
311     /**
312      * Gets the unmodifiable keySet view of the map.
313      * Changes made to the view affect this map.
314      * To simply iterate through the keys, use {@link #mapIterator()}.
315      * 
316      * @return the keySet view
317      */
318     public Set<K> keySet() {
319         return Collections.singleton(key);
320     }
321 
322     /**
323      * Gets the unmodifiable values view of the map.
324      * Changes made to the view affect this map.
325      * To simply iterate through the values, use {@link #mapIterator()}.
326      * 
327      * @return the values view
328      */
329     public Collection<V> values() {
330         return new SingletonValues<V>(this);
331     }
332 
333     /**
334      * {@inheritDoc}
335      */
336     public OrderedMapIterator<K, V> mapIterator() {
337         return new SingletonMapIterator<K, V>(this);
338     }
339 
340     /**
341      * Gets the first (and only) key in the map.
342      * 
343      * @return the key
344      */
345     public K firstKey() {
346         return getKey();
347     }
348 
349     /**
350      * Gets the last (and only) key in the map.
351      * 
352      * @return the key
353      */
354     public K lastKey() {
355         return getKey();
356     }
357 
358     /**
359      * Gets the next key after the key specified, always null.
360      * 
361      * @param key  the next key
362      * @return null always
363      */
364     public K nextKey(final K key) {
365         return null;
366     }
367 
368     /**
369      * Gets the previous key before the key specified, always null.
370      * 
371      * @param key  the next key
372      * @return null always
373      */
374     public K previousKey(final K key) {
375         return null;
376     }
377 
378     //-----------------------------------------------------------------------
379     /**
380      * Compares the specified key to the stored key.
381      * 
382      * @param key  the key to compare
383      * @return true if equal
384      */
385     protected boolean isEqualKey(final Object key) {
386         return key == null ? getKey() == null : key.equals(getKey());
387     }
388 
389     /**
390      * Compares the specified value to the stored value.
391      * 
392      * @param value  the value to compare
393      * @return true if equal
394      */
395     protected boolean isEqualValue(final Object value) {
396         return value == null ? getValue() == null : value.equals(getValue());
397     }
398 
399     //-----------------------------------------------------------------------
400     /**
401      * SingletonMapIterator.
402      */
403     static class SingletonMapIterator<K, V> implements OrderedMapIterator<K, V>, ResettableIterator<K> {
404         private final SingletonMap<K, V> parent;
405         private boolean hasNext = true;
406         private boolean canGetSet = false;
407         
408         SingletonMapIterator(final SingletonMap<K, V> parent) {
409             super();
410             this.parent = parent;
411         }
412 
413         public boolean hasNext() {
414             return hasNext;
415         }
416 
417         public K next() {
418             if (hasNext == false) {
419                 throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
420             }
421             hasNext = false;
422             canGetSet = true;
423             return parent.getKey();
424         }
425 
426         public boolean hasPrevious() {
427             return hasNext == false;
428         }
429 
430         public K previous() {
431             if (hasNext == true) {
432                 throw new NoSuchElementException(AbstractHashedMap.NO_PREVIOUS_ENTRY);
433             }
434             hasNext = true;
435             return parent.getKey();
436         }
437 
438         public void remove() {
439             throw new UnsupportedOperationException();
440         }
441 
442         public K getKey() {
443             if (canGetSet == false) {
444                 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
445             }
446             return parent.getKey();
447         }
448 
449         public V getValue() {
450             if (canGetSet == false) {
451                 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
452             }
453             return parent.getValue();
454         }
455 
456         public V setValue(final V value) {
457             if (canGetSet == false) {
458                 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
459             }
460             return parent.setValue(value);
461         }
462         
463         public void reset() {
464             hasNext = true;
465         }
466         
467         @Override
468         public String toString() {
469             if (hasNext) {
470                 return "Iterator[]";
471             }
472             return "Iterator[" + getKey() + "=" + getValue() + "]";
473         }
474     }
475     
476     /**
477      * Values implementation for the SingletonMap.
478      * This class is needed as values is a view that must update as the map updates.
479      */
480     static class SingletonValues<V> extends AbstractSet<V> implements Serializable {
481         private static final long serialVersionUID = -3689524741863047872L;
482         private final SingletonMap<?, V> parent;
483 
484         SingletonValues(final SingletonMap<?, V> parent) {
485             super();
486             this.parent = parent;
487         }
488 
489         @Override
490         public int size() {
491             return 1;
492         }
493         @Override
494         public boolean isEmpty() {
495             return false;
496         }
497         @Override
498         public boolean contains(final Object object) {
499             return parent.containsValue(object);
500         }
501         @Override
502         public void clear() {
503             throw new UnsupportedOperationException();
504         }
505         @Override
506         public Iterator<V> iterator() {
507             return new SingletonIterator<V>(parent.getValue(), false);
508         }
509     }
510     
511     //-----------------------------------------------------------------------
512     /**
513      * Clones the map without cloning the key or value.
514      *
515      * @return a shallow clone
516      */
517     @Override
518     @SuppressWarnings("unchecked")
519     public SingletonMap<K, V> clone() {
520         try {
521             return (SingletonMap<K, V>) super.clone();
522         } catch (final CloneNotSupportedException ex) {
523             throw new InternalError();
524         }
525     }
526 
527     /**
528      * Compares this map with another.
529      * 
530      * @param obj  the object to compare to
531      * @return true if equal
532      */
533     @Override
534     public boolean equals(final Object obj) {
535         if (obj == this) {
536             return true;
537         }
538         if (obj instanceof Map == false) {
539             return false;
540         }
541         final Map<?,?> other = (Map<?,?>) obj;
542         if (other.size() != 1) {
543             return false;
544         }
545         final Map.Entry<?,?> entry = other.entrySet().iterator().next();
546         return isEqualKey(entry.getKey()) && isEqualValue(entry.getValue());
547     }
548 
549     /**
550      * Gets the standard Map hashCode.
551      * 
552      * @return the hash code defined in the Map interface
553      */
554     @Override
555     public int hashCode() {
556         return (getKey() == null ? 0 : getKey().hashCode()) ^
557                (getValue() == null ? 0 : getValue().hashCode()); 
558     }
559 
560     /**
561      * Gets the map as a String.
562      * 
563      * @return a string version of the map
564      */
565     @Override
566     public String toString() {
567         return new StringBuilder(128)
568             .append('{')
569             .append(getKey() == this ? "(this Map)" : getKey())
570             .append('=')
571             .append(getValue() == this ? "(this Map)" : getValue())
572             .append('}')
573             .toString();
574     }
575 
576 }