    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 *      https://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</li>
    056 * <li>the {@code MapIterator}, see {@link #mapIterator()}</li>
    057 * <li>the {@code KeyValue} interface (just cast - no object creation)</li>
    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        /**
    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}