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