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}