    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.Set;
    027
    028import org.apache.commons.collections4.BoundedMap;
    029import org.apache.commons.collections4.KeyValue;
    030import org.apache.commons.collections4.OrderedMap;
    031import org.apache.commons.collections4.OrderedMapIterator;
    032import org.apache.commons.collections4.ResettableIterator;
    033import org.apache.commons.collections4.iterators.SingletonIterator;
    034import org.apache.commons.collections4.keyvalue.TiedMapEntry;
    035
    036/**
    037 * A {@code Map} implementation that holds a single item and is fixed size.
    038 * <p>
    039 * The single key/value pair is specified at creation.
    040 * The map is fixed size so any action that would change the size is disallowed.
    041 * However, the {@code put} or {@code setValue} methods can <i>change</i>
    042 * the value associated with the key.
    043 * </p>
    044 * <p>
    045 * If trying to remove or clear the map, an UnsupportedOperationException is thrown.
    046 * If trying to put a new mapping into the map, an  IllegalArgumentException is thrown.
    047 * The put method will only succeed if the key specified is the same as the
    048 * singleton key.
    049 * </p>
    050 * <p>
    051 * The key and value can be obtained by:
    052 * </p>
    053 * <ul>
    054 * <li>normal Map methods and views
    055 * <li>the {@code MapIterator}, see {@link #mapIterator()}
    056 * <li>the {@code KeyValue} interface (just cast - no object creation)
    057 * </ul>
    058 *
    059 * @param <K> the type of the keys in this map
    060 * @param <V> the type of the values in this map
    061 * @since 3.1
    062 */
    063public class SingletonMap<K, V>
    064        implements OrderedMap<K, V>, BoundedMap<K, V>, KeyValue<K, V>, Serializable, Cloneable {
    065
    066    /**
    067     * SingletonMapIterator.
    068     */
    069    static class SingletonMapIterator<K, V> implements OrderedMapIterator<K, V>, ResettableIterator<K> {
    070        private final SingletonMap<K, V> parent;
    071        private boolean hasNext = true;
    072        private boolean canGetSet;
    073
    074        SingletonMapIterator(final SingletonMap<K, V> parent) {
    075            this.parent = parent;
    076        }
    077
    078        @Override
    079        public K getKey() {
    080            if (!canGetSet) {
    081                throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
    082            }
    083            return parent.getKey();
    084        }
    085
    086        @Override
    087        public V getValue() {
    088            if (!canGetSet) {
    089                throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
    090            }
    091            return parent.getValue();
    092        }
    093
    094        @Override
    095        public boolean hasNext() {
    096            return hasNext;
    097        }
    098
    099        @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}