/** * '$RCSfile: OrderedMap.java,v $' * Copyright: 2000 Regents of the University of California * Authors: @authors@ * Release: @release@ * * '$Author: sambasiv $' * '$Date: 2004-04-02 21:52:52 $' * '$Revision: 1.4 $' * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the above * copyright notice and the following two paragraphs appear in all copies * of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, * ENHANCEMENTS, OR MODIFICATIONS. */ package edu.ucsb.nceas.utilities; import java.util.Map; import java.util.Set; import java.util.List; import java.util.HashMap; import java.util.Iterator; import java.util.ArrayList; import java.util.Collection; /** * The OrderedMap is similar to a java.util.Map, but it preserves the insertion * order of the keys, in the same way a java.util.List does. It is comparable * with the LinkedHashMap introduced in JDK1.4, but unavailable in JDK1.3 */ public class OrderedMap extends HashMap { private final List ordKeyList; public final static String KEY = "key"; public final static String VALUE = "value"; public final static String PAIR = "pair"; private final static String OPEN = "<"; private final static String SLASH = "/"; private final static String CLOSE = ">"; private final static String OPENNINGPAIR = OPEN+PAIR+CLOSE; private final static String CLOSINGPAIR = OPEN+SLASH+PAIR+CLOSE; private final static String OPENNINGKEY = OPEN+KEY+CLOSE; private final static String CLOSINGKEY = OPEN+SLASH+KEY+CLOSE; private final static String OPENNINGVALUE = OPEN+VALUE+CLOSE; private final static String CLOSINGVALUE = OPEN+SLASH+VALUE+CLOSE; /** * Creates a new instance of OrderedMap */ public OrderedMap() { ordKeyList = new ArrayList(); } /** 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. * * @return previous value associated with specified key, or null * if there was no mapping for key. A null return can * also indicate that the map previously associated null * with the specified key, if the implementation supports * null values. * * @param key key with which the specified value is to be associated. MAY * *NOT* BE NULL * * @param value value to be associated with the specified key. MAY BE NULL * * @throws NullPointerException if key is null */ public Object put(Object key, Object value) throws NullPointerException { checkNotNull("put()", key); if (!ordKeyList.contains(key)) ordKeyList.add(key); return super.put(key,value); } /** * Removes the mapping for this key from this map if present * * @param key key whose mapping is to be removed from the map. * * @return previous value associated with specified key, or null * if there was no mapping for key. A null return can * also indicate that the map previously associated null * with the specified key, if the implementation supports * null values. * * @throws NullPointerException if key is null */ public Object remove(Object key) throws NullPointerException { checkNotNull("remove(key): ", key); ordKeyList.remove(key); return super.remove(key); } // Bulk Operations /** * The putAll() method defined in the Map interface expects a Map as * the input parameter. NOTE, however, that for this method to make * sense for the OrderedMap, the putAll() method must receive * another OrderedMap object as input. Therefore, this method's * signature has a Map as input, but the method body carries out * further validation to ensure that the passed object can actually be cast * to an instance of OrderedMap. If not, the method throws an * UnsupportedOperationException. * * @param m a Map object that can be cast to an instance of * OrderedMap. * * @throws UnsupportedOperationException, if the passed Map cannot be cast to * an instance of OrderedMap. */ public void putAll(Map m) throws UnsupportedOperationException { if (m!=null && (m instanceof OrderedMap)) { Iterator keysIt = m.keySet().iterator(); if (keysIt==null) return; Object nextKey = null; while (keysIt.hasNext()) { nextKey = keysIt.next(); if (nextKey==null) continue; else this.put(nextKey, m.get(nextKey)); } } else { throwUnsupportedOperationException( "Map object received by putAll() must be an instance of OrderedMap" +((m==null)? " - *NULL* Map received!!" : " - received: "+m)); } } /** * The removeAll() method defined in the Map interface expects a Map as * the input parameter. NOTE, however, that for this method to make * sense for the OrderedMap, the removeAll() method must receive * another OrderedMap object as input. Therefore, this method's * signature has a Map as input, but the method body carries out * further validation to ensure that the passed object can actually be cast * to an instance of OrderedMap. If not, the method throws an * UnsupportedOperationException. * * This method is used to remove all elements that belong to the given set. * * @param m a Map object that can be cast to an instance of * OrderedMap. * * @throws UnsupportedOperationException, if the passed Map cannot be cast to * an instance of OrderedMap. */ public void removeAll(Map m) throws UnsupportedOperationException { if (m!=null && (m instanceof OrderedMap)) { Iterator keysIt = m.keySet().iterator(); if (keysIt==null) return; Object nextKey = null; while (keysIt.hasNext()) { nextKey = keysIt.next(); if (nextKey==null) continue; else this.remove(nextKey); } } else { throwUnsupportedOperationException( "Map object received by removeAll() must be an instance of OrderedMap" +((m==null)? " - *NULL* Map received!!" : " - received: "+m)); } } /** * Removes all mappings from this map */ public void clear(){ ordKeyList.clear(); super.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 Iterator.remove, Set.remove, * removeAll retainAll, and clear operations. * It does not support the add or addAll operations. * * @return a set view of the keys contained in this map. */ public Set keySet(){ return new Set() { public int size() { return ordKeyList.size(); } public boolean isEmpty() { return ordKeyList.isEmpty(); } public boolean contains(Object o) { return ordKeyList.contains(o);} public Iterator iterator() { return ordKeyList.iterator(); } public Object[] toArray() { return ordKeyList.toArray(); } public Object[] toArray(Object a[]) { return ordKeyList.toArray(a); } public boolean containsAll(Collection c){ return ordKeyList.isEmpty(); } public boolean equals(Object o) { return ordKeyList.equals(o); } public int hashCode() { return ordKeyList.hashCode(); } public boolean add(Object o){ throwUnsupportedOperationException( "add() not supported; this Set is immutable"); return false; } public boolean remove(Object o){ throwUnsupportedOperationException( "remove() not supported; this Set is immutable"); return false; } public boolean addAll(Collection c){ throwUnsupportedOperationException( "addAll() not supported; this Set is immutable"); return false; } public boolean retainAll(Collection c){ throwUnsupportedOperationException( "retainAll() not supported; this Set is immutable"); return false; } public boolean removeAll(Collection c){ throwUnsupportedOperationException( "removeAll() not supported; this Set is immutable"); return false; } public void clear(){ throwUnsupportedOperationException( "clear() not supported; this Set is immutable"); } }; } // private Collection orderedValues = new ArrayList(); // /** * returns a java.util.Collection view of the values in * this OrderedMap * * @return a java.util.Collection view of the values in this * OrderedMap */ public Collection values(){ orderedValues.clear(); Iterator it = ordKeyList.iterator(); while (it.hasNext()) { orderedValues.add(super.get(it.next())); } return orderedValues; } // StringBuffer toStringBuff = new StringBuffer(); // /** * Returns a string representation of the keys & values contained in this map * * @return a string representation of the keys & values contained in this map */ public String toString(){ Iterator it = keySet().iterator(); if (it==null) return null; toStringBuff.delete(0,toStringBuff.length()); toStringBuff.append("\n\n* * * Begin OrderedMap * * *\n\n"); Object nextKey = null; Object nextVal = null; while (it.hasNext()) { nextKey = it.next(); if (nextKey==null) { toStringBuff.append("**NULL KEY**\n"); continue; } else { toStringBuff.append(nextKey); } toStringBuff.append("\t = \t"); nextVal = get(nextKey); if (nextVal==null) toStringBuff.append("**NULL VALUE**"); else toStringBuff.append(nextVal); toStringBuff.append("\n"); } toStringBuff.append("\n* * * End OrderedMap * * *\n"); return toStringBuff.toString(); } /** * Resturns a XML representation of the keys & values contained in this map * * ... * ... * */ public String toXML() { Iterator it = keySet().iterator(); if (it==null) return null; toStringBuff.delete(0,toStringBuff.length()); Object nextKey = null; Object nextVal = null; while (it.hasNext()) { nextKey = it.next(); if (nextKey==null) { continue; } else { toStringBuff.append(OPENNINGPAIR); toStringBuff.append(OPENNINGKEY+nextKey+CLOSINGKEY); } nextVal = get(nextKey); if (nextVal==null) toStringBuff.append(OPENNINGVALUE+"**NULL VALUE**"+CLOSINGVALUE); else toStringBuff.append(OPENNINGVALUE+nextVal+CLOSINGVALUE); toStringBuff.append(CLOSINGPAIR); toStringBuff.append("\n"); } return toStringBuff.toString(); } /** * simply calls the entrySet method in the superclass - but * note that *ORDER IS NOT GUARANTEED IN THE SET*. * Javadoc for this method from Map: * * Returns a collection view of the mappings contained in this map. Each * element in the returned collection is a Map.Entry. The collection is * backed by the map, so changes to the map are reflected in the collection, * and vice-versa. The collection supports element removal, which removes the * corresponding mapping from the map, via the Iterator.remove, * Collection.remove, removeAll, retainAll, and clear operations. It does not * support the add or addAll operations. * * @return a collection view of the mappings contained in this map */ public Set entrySet(){ return super.entrySet(); } /** * throws a NullPointerException if arg is null * * @param origin String describing calling function - used for debugging in * Exception message * * @param arg the Object to be tested for null value * * @throws NullPointerException if arg is null */ private void checkNotNull(String origin, Object arg) throws NullPointerException { if (arg==null) { throwNullPointerException(origin+"\n* * * Argument must not be null!\n"); } } /** * throws a NullPointerException if key or value argument is null (or if both * are null) * * @param origin String describing calling function - used for debugging in * Exception message * * @param arg the "key" Object to be tested for null value * * @param arg the "value" Object to be tested for null value * * @throws NullPointerException if key or value argument is null (or * if both are null) */ private void checkNotNull(String origin, Object key, Object value) throws NullPointerException { if (key==null || value==null) { String keyNull = (key==null)? "* * * Key must not be null!\n" : ""; String valNull = (value==null)? "* * * Value must not be null!\n" : ""; throwNullPointerException(origin+" received key=" +key+" & value="+value +"\n"+keyNull+valNull); } } /** * convenience method to throw a NullPointerException, with the String * message provided * * @param msg The String message to use in constructing the * NullPointerException * * @throws NullPointerException */ private void throwNullPointerException(String msg) throws NullPointerException { NullPointerException npe = new NullPointerException(msg); npe.fillInStackTrace(); throw npe; } /** * convenience method to throw a UnsupportedOperationException, with the * String message provided * * @param msg The String message to use in constructing the * UnsupportedOperationException * * @throws UnsupportedOperationException */ private void throwUnsupportedOperationException(String msg) throws UnsupportedOperationException { UnsupportedOperationException e = new UnsupportedOperationException(msg); e.fillInStackTrace(); throw e; } }