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