package jadex.commons.collection; import jadex.commons.SUtil; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * A nested map refers to parent maps for entries * not found in this map. * Modifications of this map do not affect the parent maps. */ // todo: implement views. // todo: implement correct hashCode()/size() when keys are overriden. public class NestedMap implements Map, java.io.Serializable { //-------- attributes -------- /** The local map. */ protected Map local; /** The parent maps. */ protected Map[] parents; //-------- constructors -------- /** * Create a nested map, referring to the specified parent map. * @param parent The parent map. */ public NestedMap(Map parent) { this(new Map[]{parent}); } /** * Create a nested map, referring to the specified parent maps. * @param parents The parent maps. */ public NestedMap(Map[] parents) { this(parents, new HashMap()); } /** * Create a nested map, referring to the specified parent map, * using the given map for storing local mappings. * @param parents The parent map. * @param local The map for local mappings. */ protected NestedMap(Map[] parents, Map local) { assert local!=null; assert parents!=null; for(int i=0; i<parents.length; i++) assert parents[i]!=null: this; this.parents = new Map[parents.length]; System.arraycopy(parents, 0, this.parents, 0, parents.length); this.local = local; } //-------- methods -------- /** * Get the map containing the local mappings. */ public Map getLocalMap() { return local; } //-------- Map methods -------- // Query Operations /** * Returns the number of key-value mappings in this map. If the * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns * <tt>Integer.MAX_VALUE</tt>. * Note, complexity of the implementation is not constant but linear * to the number of entries in the contained maps! * @return the number of key-value mappings in this map. */ public int size() { // To determine size, build union of key sets. Set keys = new HashSet(local.keySet()); for(int i=0; i<parents.length; i++) { keys.addAll(parents[i].keySet()); } return keys.size(); } /** * Returns <tt>true</tt> if this map contains no key-value mappings. * @return <tt>true</tt> if this map contains no key-value mappings. */ public boolean isEmpty() { boolean empty = local.isEmpty(); for(int i=0; empty && i<parents.length; i++) { empty = parents[i].isEmpty(); } return empty; } /** * Returns <tt>true</tt> if this map contains a mapping for the specified * key. More formally, returns <tt>true</tt> if and only if * this map contains at a mapping for a key <tt>k</tt> such that * <tt>(key==null ? k==null : key.equals(k))</tt>. (There can be * at most one such mapping.) * * @param key key whose presence in this map is to be tested. * @return <tt>true</tt> if this map contains a mapping for the specified * key. * * @throws ClassCastException if the key is of an inappropriate type for * this map (optional). * @throws NullPointerException if the key is <tt>null</tt> and this map * does not not permit <tt>null</tt> keys (optional). */ public boolean containsKey(Object key) { boolean contains = local.containsKey(key); for(int i=0; !contains && i<parents.length; i++) { contains = parents[i].containsKey(key); } return contains; } /** * Returns <tt>true</tt> if this map maps one or more keys to the * specified value. More formally, returns <tt>true</tt> if and only if * this map contains at least one mapping to a value <tt>v</tt> such that * <tt>(value==null ? v==null : value.equals(v))</tt>. This operation * will probably require time linear in the map size for most * implementations of the <tt>Map</tt> interface. * * @param value value whose presence in this map is to be tested. * @return <tt>true</tt> if this map maps one or more keys to the * specified value. * @throws ClassCastException if the value is of an inappropriate type for * this map (optional). * @throws NullPointerException if the value is <tt>null</tt> and this map * does not not permit <tt>null</tt> values (optional). */ public boolean containsValue(Object value) { boolean contains = local.containsValue(value); for(int i=0; !contains && i<parents.length; i++) { contains = parents[i].containsValue(value); } return contains; } /** * Returns the value to which this map maps the specified key. Returns * <tt>null</tt> if the map contains no mapping for this key. A return * value of <tt>null</tt> does not <i>necessarily</i> indicate that the * map contains no mapping for the key; it's also possible that the map * explicitly maps the key to <tt>null</tt>. The <tt>containsKey</tt> * operation may be used to distinguish these two cases. * * <p>More formally, if this map contains a mapping from a key * <tt>k</tt> to a value <tt>v</tt> such that <tt>(key==null ? k==null : * key.equals(k))</tt>, then this method returns <tt>v</tt>; otherwise * it returns <tt>null</tt>. (There can be at most one such mapping.) * * @param key key whose associated value is to be returned. * @return the value to which this map maps the specified key, or * <tt>null</tt> if the map contains no mapping for this key. * * @throws ClassCastException if the key is of an inappropriate type for * this map (optional). * @throws NullPointerException key is <tt>null</tt> and this map does not * not permit <tt>null</tt> keys (optional). * * @see #containsKey(Object) */ public Object get(Object key) { // Have to check for containsKey, as stored value may be null. Object value = null; boolean found; if(found=local.containsKey(key)) { value = local.get(key); } for(int i=0; !found && i<parents.length; i++) { if(found=parents[i].containsKey(key)) { value = parents[i].get(key); } } return value; } // Modification Operations /** * Associates the specified value with the specified key in this map * (optional operation). If the map previously contained a mapping for * this key, the old value is replaced by the specified value. (A map * <tt>m</tt> is said to contain a mapping for a key <tt>k</tt> if and only * if {@link #containsKey(Object) m.containsKey(k)} would return * <tt>true</tt>.)) * * @param key key with which the specified value is to be associated. * @param value value to be associated with the specified key. * @return previous value associated with specified key, or <tt>null</tt> * if there was no mapping for key. A <tt>null</tt> return can * also indicate that the map previously associated <tt>null</tt> * with the specified key, if the implementation supports * <tt>null</tt> values. * * @throws UnsupportedOperationException if the <tt>put</tt> operation is * not supported by this map. * @throws ClassCastException if the class of the specified key or value * prevents it from being stored in this map. * @throws IllegalArgumentException if some aspect of this key or value * prevents it from being stored in this map. * @throws NullPointerException this map does not permit <tt>null</tt> * keys or values, and the specified key or value is * <tt>null</tt>. */ public Object put(Object key, Object value) { return local.put(key, value); } /** * Removes the mapping for this key from this map if it is present * (optional operation). More formally, if this map contains a mapping * from key <tt>k</tt> to value <tt>v</tt> such that * <code>(key==null ? k==null : key.equals(k))</code>, that mapping * is removed. (The map can contain at most one such mapping.) * * <p>Returns the value to which the map previously associated the key, or * <tt>null</tt> if the map contained no mapping for this key. (A * <tt>null</tt> return can also indicate that the map previously * associated <tt>null</tt> with the specified key if the implementation * supports <tt>null</tt> values.) The map will not contain a mapping for * the specified key once the call returns. * * @param key key whose mapping is to be removed from the map. * @return previous value associated with specified key, or <tt>null</tt> * if there was no mapping for key. * * @throws ClassCastException if the key is of an inappropriate type for * this map (optional). * @throws NullPointerException if the key is <tt>null</tt> and this map * does not not permit <tt>null</tt> keys (optional). * @throws UnsupportedOperationException if the <tt>remove</tt> method is * not supported by this map. */ public Object remove(Object key) { return local.remove(key); } // Bulk Operations /** * Copies all of the mappings from the specified map to this map * (optional operation). The effect of this call is equivalent to that * of calling {@link #put(Object,Object) put(k, v)} on this map once * for each mapping from key <tt>k</tt> to value <tt>v</tt> in the * specified map. The behavior of this operation is unspecified if the * specified map is modified while the operation is in progress. * * @param t Mappings to be stored in this map. * * @throws UnsupportedOperationException if the <tt>putAll</tt> method is * not supported by this map. * * @throws ClassCastException if the class of a key or value in the * specified map prevents it from being stored in this map. * * @throws IllegalArgumentException some aspect of a key or value in the * specified map prevents it from being stored in this map. * @throws NullPointerException the specified map is <tt>null</tt>, or if * this map does not permit <tt>null</tt> keys or values, and the * specified map contains <tt>null</tt> keys or values. */ public void putAll(Map t) { Iterator it = t.keySet().iterator(); while(it.hasNext()) { Object key = it.next(); put(key, t.get(key)); } } /** * Removes all mappings from this map (optional operation). * * @throws UnsupportedOperationException if clear is not supported by this * map. */ public void clear() { local.clear(); } // Views /** * Returns a set view of the keys contained in this map. The set is * backed by the map, so changes to the map are reflected in the set, and * vice-versa. If the map is modified while an iteration over the set is * in progress, the results of the iteration are undefined. The set * supports element removal, which removes the corresponding mapping from * the map, via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, * <tt>removeAll</tt> <tt>retainAll</tt>, and <tt>clear</tt> operations. * It does not support the add or <tt>addAll</tt> operations. * * @return a set view of the keys contained in this map. */ public Set keySet() { // todo: return a view // todo: eliminate dublicates Set ret = new HashSet(local.keySet()); for(int i=0; i<parents.length; i++) ret.addAll(parents[i].keySet()); return ret; //throw new UnsupportedOperationException("keySet() not supported for NestedMap."); } /** * Returns a collection view of the values contained in this map. The * collection is backed by the map, so changes to the map are reflected in * the collection, and vice-versa. If the map is modified while an * iteration over the collection is in progress, the results of the * iteration are undefined. The collection supports element removal, * which removes the corresponding mapping from the map, via the * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, * <tt>removeAll</tt>, <tt>retainAll</tt> and <tt>clear</tt> operations. * It does not support the add or <tt>addAll</tt> operations. * * @return a collection view of the values contained in this map. */ public Collection values() { // todo: return a view // todo: eliminate dublicates Collection ret = new HashSet(local.values()); for(int i=0; i<parents.length; i++) ret.addAll(parents[i].values()); return ret; //throw new UnsupportedOperationException("values() not supported for NestedMap."); } /** * Returns a set view of the mappings contained in this map. Each element * in the returned set is a Map.Entry. The set is backed by the * map, so changes to the map are reflected in the set, and vice-versa. * If the map is modified while an iteration over the set is in progress, * the results of the iteration are undefined. The set supports element * removal, which removes the corresponding mapping from the map, via the * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>, * <tt>retainAll</tt> and <tt>clear</tt> operations. It does not support * the <tt>add</tt> or <tt>addAll</tt> operations. * * @return a set view of the mappings contained in this map. */ public Set entrySet() { // todo: eliminate dublicates Set ret = local.entrySet(); for(int i=0; i<parents.length; i++) ret.addAll(parents[i].entrySet()); return ret; //throw new UnsupportedOperationException("entrySet() not supported for NestedMap."); } // Comparison and hashing /** * Compares the specified object with this map for equality. Returns * <tt>true</tt> if the given object is also a map and the two Maps * represent the same mappings. More formally, two maps <tt>t1</tt> and * <tt>t2</tt> represent the same mappings if * <tt>t1.entrySet().equals(t2.entrySet())</tt>. This ensures that the * <tt>equals</tt> method works properly across different implementations * of the <tt>Map</tt> interface. * * @param o object to be compared for equality with this map. * @return <tt>true</tt> if the specified object is equal to this map. */ public boolean equals(Object o) { return (o instanceof Map) && hashCode()==o.hashCode(); } /** * Returns the hash code value for this map. The hash code of a map * is defined to be the sum of the hashCodes of each entry in the map's * entrySet view. This ensures that <tt>t1.equals(t2)</tt> implies * that <tt>t1.hashCode()==t2.hashCode()</tt> for any two maps * <tt>t1</tt> and <tt>t2</tt>, as required by the general * contract of Object.hashCode. * * @return the hash code value for this map. * @see Object#hashCode() * @see Object#equals(Object) * @see #equals(Object) */ public int hashCode() { // To determine hash code, build union of entry sets. // Hash code is obtained as sum of the entries' hash codes. Set entries = new HashSet(local.entrySet()); for(int i=0; i<parents.length; i++) { entries.addAll(parents[i].entrySet()); } return entries.hashCode(); } /** * Create a string representation of this map. */ public String toString() { return "NestedMap(local="+local+", parents="+SUtil.arrayToString(parents)+")"; } }