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