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