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