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