001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.map;
018
019import java.io.Serializable;
020import java.util.AbstractSet;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Iterator;
024import java.util.Map;
025import java.util.NoSuchElementException;
026import java.util.Objects;
027import java.util.Set;
028
029import org.apache.commons.collections4.BoundedMap;
030import org.apache.commons.collections4.KeyValue;
031import org.apache.commons.collections4.OrderedMap;
032import org.apache.commons.collections4.OrderedMapIterator;
033import org.apache.commons.collections4.ResettableIterator;
034import org.apache.commons.collections4.iterators.SingletonIterator;
035import org.apache.commons.collections4.keyvalue.TiedMapEntry;
036
037/**
038 * A {@code Map} implementation that holds a single item and is fixed size.
039 * <p>
040 * The single key/value pair is specified at creation.
041 * The map is fixed size so any action that would change the size is disallowed.
042 * However, the {@code put} or {@code setValue} methods can <em>change</em>
043 * the value associated with the key.
044 * </p>
045 * <p>
046 * If trying to remove or clear the map, an UnsupportedOperationException is thrown.
047 * If trying to put a new mapping into the map, an  IllegalArgumentException is thrown.
048 * The put method will only succeed if the key specified is the same as the
049 * singleton key.
050 * </p>
051 * <p>
052 * The key and value can be obtained by:
053 * </p>
054 * <ul>
055 * <li>normal Map methods and views
056 * <li>the {@code MapIterator}, see {@link #mapIterator()}
057 * <li>the {@code KeyValue} interface (just cast - no object creation)
058 * </ul>
059 *
060 * @param <K> the type of the keys in this map
061 * @param <V> the type of the values in this map
062 * @since 3.1
063 */
064public class SingletonMap<K, V>
065        implements OrderedMap<K, V>, BoundedMap<K, V>, KeyValue<K, V>, Serializable, Cloneable {
066
067    /**
068     * SingletonMapIterator.
069     */
070    static class SingletonMapIterator<K, V> implements OrderedMapIterator<K, V>, ResettableIterator<K> {
071        private final SingletonMap<K, V> parent;
072        private boolean hasNext = true;
073        private boolean canGetSet;
074
075        SingletonMapIterator(final SingletonMap<K, V> parent) {
076            this.parent = parent;
077        }
078
079        @Override
080        public K getKey() {
081            if (!canGetSet) {
082                throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
083            }
084            return parent.getKey();
085        }
086
087        @Override
088        public V getValue() {
089            if (!canGetSet) {
090                throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
091            }
092            return parent.getValue();
093        }
094
095        @Override
096        public boolean hasNext() {
097            return hasNext;
098        }
099
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}