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