/* * Copyright 2003-2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.collections.map; import java.io.Serializable; import java.util.AbstractSet; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.apache.commons.collections.BoundedMap; import org.apache.commons.collections.KeyValue; import org.apache.commons.collections.MapIterator; import org.apache.commons.collections.OrderedMap; import org.apache.commons.collections.OrderedMapIterator; import org.apache.commons.collections.ResettableIterator; import org.apache.commons.collections.iterators.SingletonIterator; import org.apache.commons.collections.keyvalue.TiedMapEntry; /** * A <code>Map</code> implementation that holds a single item and is fixed size. * <p> * The single key/value pair is specified at creation. * The map is fixed size so any action that would change the size is disallowed. * However, the <code>put</code> or <code>setValue</code> methods can <i>change</i> * the value associated with the key. * <p> * If trying to remove or clear the map, an UnsupportedOperationException is thrown. * If trying to put a new mapping into the map, an IllegalArgumentException is thrown. * The put method will only suceed if the key specified is the same as the * singleton key. * <p> * The key and value can be obtained by: * <ul> * <li>normal Map methods and views * <li>the <code>MapIterator</code>, see {@link #mapIterator()} * <li>the <code>KeyValue</code> interface (just cast - no object creation) * </ul> * * @since Commons Collections 3.1 * @version $Revision: 155406 $ $Date: 2005-02-26 12:55:26 +0000 (Sat, 26 Feb 2005) $ * * @author Stephen Colebourne */ public class SingletonMap implements OrderedMap, BoundedMap, KeyValue, Serializable, Cloneable { /** Serialization version */ private static final long serialVersionUID = -8931271118676803261L; /** Singleton key */ private final Object key; /** Singleton value */ private Object value; /** * Constructor that creates a map of <code>null</code> to <code>null</code>. */ public SingletonMap() { super(); this.key = null; } /** * Constructor specifying the key and value. * * @param key the key to use * @param value the value to use */ public SingletonMap(Object key, Object value) { super(); this.key = key; this.value = value; } /** * Constructor specifying the key and value as a <code>KeyValue</code>. * * @param keyValue the key value pair to use */ public SingletonMap(KeyValue keyValue) { super(); this.key = keyValue.getKey(); this.value = keyValue.getValue(); } /** * Constructor specifying the key and value as a <code>MapEntry</code>. * * @param mapEntry the mapEntry to use */ public SingletonMap(Map.Entry mapEntry) { super(); this.key = mapEntry.getKey(); this.value = mapEntry.getValue(); } /** * Constructor copying elements from another map. * * @param map the map to copy, must be size 1 * @throws NullPointerException if the map is null * @throws IllegalArgumentException if the size is not 1 */ public SingletonMap(Map map) { super(); if (map.size() != 1) { throw new IllegalArgumentException("The map size must be 1"); } Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); this.key = entry.getKey(); this.value = entry.getValue(); } // KeyValue //----------------------------------------------------------------------- /** * Gets the key. * * @return the key */ public Object getKey() { return key; } /** * Gets the value. * * @return the value */ public Object getValue() { return value; } /** * Sets the value. * * @param value the new value to set * @return the old value */ public Object setValue(Object value) { Object old = this.value; this.value = value; return old; } // BoundedMap //----------------------------------------------------------------------- /** * Is the map currently full, always true. * * @return true always */ public boolean isFull() { return true; } /** * Gets the maximum size of the map, always 1. * * @return 1 always */ public int maxSize() { return 1; } // Map //----------------------------------------------------------------------- /** * Gets the value mapped to the key specified. * * @param key the key * @return the mapped value, null if no match */ public Object get(Object key) { if (isEqualKey(key)) { return value; } return null; } /** * Gets the size of the map, always 1. * * @return the size of 1 */ public int size() { return 1; } /** * Checks whether the map is currently empty, which it never is. * * @return false always */ public boolean isEmpty() { return false; } //----------------------------------------------------------------------- /** * Checks whether the map contains the specified key. * * @param key the key to search for * @return true if the map contains the key */ public boolean containsKey(Object key) { return (isEqualKey(key)); } /** * Checks whether the map contains the specified value. * * @param value the value to search for * @return true if the map contains the key */ public boolean containsValue(Object value) { return (isEqualValue(value)); } //----------------------------------------------------------------------- /** * Puts a key-value mapping into this map where the key must match the existing key. * <p> * An IllegalArgumentException is thrown if the key does not match as the map * is fixed size. * * @param key the key to set, must be the key of the map * @param value the value to set * @return the value previously mapped to this key, null if none * @throws IllegalArgumentException if the key does not match */ public Object put(Object key, Object value) { if (isEqualKey(key)) { return setValue(value); } throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size singleton"); } /** * Puts the values from the specified map into this map. * <p> * The map must be of size 0 or size 1. * If it is size 1, the key must match the key of this map otherwise an * IllegalArgumentException is thrown. * * @param map the map to add, must be size 0 or 1, and the key must match * @throws NullPointerException if the map is null * @throws IllegalArgumentException if the key does not match */ public void putAll(Map map) { switch (map.size()) { case 0: return; case 1: Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); put(entry.getKey(), entry.getValue()); return; default: throw new IllegalArgumentException("The map size must be 0 or 1"); } } /** * Unsupported operation. * * @param key the mapping to remove * @return the value mapped to the removed key, null if key not in map * @throws UnsupportedOperationException always */ public Object remove(Object key) { throw new UnsupportedOperationException(); } /** * Unsupported operation. */ public void clear() { throw new UnsupportedOperationException(); } //----------------------------------------------------------------------- /** * Gets the entrySet view of the map. * Changes made via <code>setValue</code> affect this map. * To simply iterate through the entries, use {@link #mapIterator()}. * * @return the entrySet view */ public Set entrySet() { Map.Entry entry = new TiedMapEntry(this, getKey()); return Collections.singleton(entry); } /** * Gets the unmodifiable keySet view of the map. * Changes made to the view affect this map. * To simply iterate through the keys, use {@link #mapIterator()}. * * @return the keySet view */ public Set keySet() { return Collections.singleton(key); } /** * Gets the unmodifiable values view of the map. * Changes made to the view affect this map. * To simply iterate through the values, use {@link #mapIterator()}. * * @return the values view */ public Collection values() { return new SingletonValues(this); } /** * Gets an iterator over the map. * Changes made to the iterator using <code>setValue</code> affect this map. * The <code>remove</code> method is unsupported. * <p> * A MapIterator returns the keys in the map. It also provides convenient * methods to get the key and value, and set the value. * It avoids the need to create an entrySet/keySet/values object. * It also avoids creating the Map Entry object. * * @return the map iterator */ public MapIterator mapIterator() { return new SingletonMapIterator(this); } // OrderedMap //----------------------------------------------------------------------- /** * Obtains an <code>OrderedMapIterator</code> over the map. * <p> * A ordered map iterator is an efficient way of iterating over maps * in both directions. * * @return an ordered map iterator */ public OrderedMapIterator orderedMapIterator() { return new SingletonMapIterator(this); } /** * Gets the first (and only) key in the map. * * @return the key */ public Object firstKey() { return getKey(); } /** * Gets the last (and only) key in the map. * * @return the key */ public Object lastKey() { return getKey(); } /** * Gets the next key after the key specified, always null. * * @param key the next key * @return null always */ public Object nextKey(Object key) { return null; } /** * Gets the previous key before the key specified, always null. * * @param key the next key * @return null always */ public Object previousKey(Object key) { return null; } //----------------------------------------------------------------------- /** * Compares the specified key to the stored key. * * @param key the key to compare * @return true if equal */ protected boolean isEqualKey(Object key) { return (key == null ? getKey() == null : key.equals(getKey())); } /** * Compares the specified value to the stored value. * * @param value the value to compare * @return true if equal */ protected boolean isEqualValue(Object value) { return (value == null ? getValue() == null : value.equals(getValue())); } //----------------------------------------------------------------------- /** * SingletonMapIterator. */ static class SingletonMapIterator implements OrderedMapIterator, ResettableIterator { private final SingletonMap parent; private boolean hasNext = true; private boolean canGetSet = false; SingletonMapIterator(SingletonMap parent) { super(); this.parent = parent; } public boolean hasNext() { return hasNext; } public Object next() { if (hasNext == false) { throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); } hasNext = false; canGetSet = true; return parent.getKey(); } public boolean hasPrevious() { return (hasNext == false); } public Object previous() { if (hasNext == true) { throw new NoSuchElementException(AbstractHashedMap.NO_PREVIOUS_ENTRY); } hasNext = true; return parent.getKey(); } public void remove() { throw new UnsupportedOperationException(); } public Object getKey() { if (canGetSet == false) { throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); } return parent.getKey(); } public Object getValue() { if (canGetSet == false) { throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); } return parent.getValue(); } public Object setValue(Object value) { if (canGetSet == false) { throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); } return parent.setValue(value); } public void reset() { hasNext = true; } public String toString() { if (hasNext) { return "Iterator[]"; } else { return "Iterator[" + getKey() + "=" + getValue() + "]"; } } } /** * Values implementation for the SingletonMap. * This class is needed as values is a view that must update as the map updates. */ static class SingletonValues extends AbstractSet implements Serializable { private static final long serialVersionUID = -3689524741863047872L; private final SingletonMap parent; SingletonValues(SingletonMap parent) { super(); this.parent = parent; } public int size() { return 1; } public boolean isEmpty() { return false; } public boolean contains(Object object) { return parent.containsValue(object); } public void clear() { throw new UnsupportedOperationException(); } public Iterator iterator() { return new SingletonIterator(parent.getValue(), false); } } //----------------------------------------------------------------------- /** * Clones the map without cloning the key or value. * * @return a shallow clone */ public Object clone() { try { SingletonMap cloned = (SingletonMap) super.clone(); return cloned; } catch (CloneNotSupportedException ex) { throw new InternalError(); } } /** * Compares this map with another. * * @param obj the object to compare to * @return true if equal */ public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Map == false) { return false; } Map other = (Map) obj; if (other.size() != 1) { return false; } Map.Entry entry = (Map.Entry) other.entrySet().iterator().next(); return isEqualKey(entry.getKey()) && isEqualValue(entry.getValue()); } /** * Gets the standard Map hashCode. * * @return the hash code defined in the Map interface */ public int hashCode() { return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); } /** * Gets the map as a String. * * @return a string version of the map */ public String toString() { return new StringBuffer(128) .append('{') .append((getKey() == this ? "(this Map)" : getKey())) .append('=') .append((getValue() == this ? "(this Map)" : getValue())) .append('}') .toString(); } }