    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}