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