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}