/*
 * @(#)StringObjectMap.java    2.3.2   2 January 2005
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.quick;

/**
 * <P><CODE>StringObjectMap</CODE> encapsulates a structure that maintains
 * a collection of <CODE>String</CODE> and <CODE>Object</CODE> pairs in
 * such a way that the original order of pair entry is preserved and it is
 * efficient to map a string to an object and vice versa.</P>
 *
 * @author  Richard Rasala
 * @version 2.3
 * @since   2.3
 */
public final class StringObjectMap {
    
    /** The string to object map. */
    private QuickTreeMap stringToObjectMap = new QuickTreeMap();
    
    /** The object to string map. */
    private QuickHashMap objectToStringMap = new QuickHashMap();
    
    /** The vector of string-object pairs in order. */
    private QuickVector  pairList          = new QuickVector();
    
    
    /**
     * The default constructor.
     *
     * @see #StringObjectMap(Object[][])
     * @see #StringObjectMap(StringObjectPair[])
     */
    public StringObjectMap() { }
    
    
    /**
     * <P>The constructor with an array of string-object pairs as a
     * 2-dimensional array of <CODE>Object</CODE>.</P>
     *
     * @param pairs the array of string-object pairs
     * @see #StringObjectMap()
     * @see #StringObjectMap(StringObjectPair[])
     */
    public StringObjectMap(Object[][] pairs) {
        addPairs(pairs);
    }
    
    
    /**
     * <P>The constructor with an array of <CODE>StringObjectPair</CODE>s.</P>
     *
     * @param pairs the array of string-object pairs
     * @see #StringObjectMap()
     * @see #StringObjectMap(StringObjectPair[])
     */
    public StringObjectMap(StringObjectPair[] pairs) {
        addPairs(pairs);
    }
    
    
    /**
     * <P>Adds the given <CODE>String</CODE> and <CODE>Object</CODE>
     * pair to the <CODE>StringObjectMap</CODE>.</P>
     *
     * <P>The string and object must both be non-<CODE>null</CODE>
     * or else does nothing.</P>
     *
     * <P>Neither the string nor the object can already appear in
     * the corresponding slot of this map.  Does nothing if such a
     * duplication is detected. </P>
     *
     * @param  string the string to enter into the map
     * @param  object the associated object to enter into the map
     * @return whether or not the operation was successful
     * @see #addPair(StringObjectPair)
     * @see #addPair(int, String, Object)
     * @see #addPair(int, StringObjectPair)
     */
    public boolean addPair(String string, Object object) {
        return addPair(StringObjectPair.makeStringObjectPair(string, object));
    }
    
    
    /**
     * <P>Adds the given <CODE>StringObjectPair</CODE> pair to the
     * <CODE>StringObjectMap</CODE>.</P>
     *
     * <P>If the pair is <CODE>null</CODE> then does nothing.</P>
     *
     * <P>Neither the string nor the object in this pair can already
     * appear in the corresponding slot of this map.  Does nothing
     * if such a duplication is detected. </P>
     *
     * @param  pair the string-object pair to enter into the map
     * @return whether or not the operation was successful
     * @see #addPair(String, Object)
     * @see #addPair(int, String, Object)
     * @see #addPair(int, StringObjectPair)
     */
    public boolean addPair(StringObjectPair pair) {
        if (pair == null)
            return false;
        
        String string = pair.getString();
        
        if (containsString(string))
            return false;
        
        Object object = pair.getObject();
        
        if (containsObject(object))
            return false;
        
        stringToObjectMap.put(string, object);
        objectToStringMap.put(object, string);
        pairList.add(pair);
        
        return true;
    }
    
    
    /**
     * <P>Adds the given <CODE>String</CODE> and <CODE>Object</CODE>
     * pair to the <CODE>StringObjectMap</CODE> at the given index.</P>
     *
     * <P>The string and object must both be non-<CODE>null</CODE>
     * or else does nothing.</P>
     *
     * <P>Neither the string nor the object can already appear in
     * the corresponding slot of this map.  Does nothing if such a
     * duplication is detected. </P>
     *
     * <P>Does nothing if (index < 0) or if (index > size()).</P>
     *
     * @param  index  the insert position
     * @param  string the string to enter into the map
     * @param  object the associated object to enter into the map
     * @return whether or not the operation was successful
     * @see #addPair(String, Object)
     * @see #addPair(StringObjectPair)
     * @see #addPair(int, StringObjectPair)
     */
    public boolean addPair(int index, String string, Object object) {
        return addPair(index, StringObjectPair.makeStringObjectPair(string, object));
    }
    
    
    /**
     * <P>Adds the given <CODE>StringObjectPair</CODE> pair to the
     * <CODE>StringObjectMap</CODE> at the given index.</P>
     *
     * <P>If the pair is <CODE>null</CODE> then does nothing.</P>
     *
     * <P>Neither the string nor the object in this pair can already
     * appear in the corresponding slot of this map.  Does nothing
     * if such a duplication is detected. </P>
     *
     * <P>Does nothing if (index < 0) or if (index > size()).</P>
     *
     * @param  index the insert position
     * @param  pair  the string-object pair to enter into the map
     * @return whether or not the operation was successful
     * @see #addPair(String, Object)
     * @see #addPair(StringObjectPair)
     * @see #addPair(int, String, Object)
     */
    public boolean addPair(int index, StringObjectPair pair) {
        if ((index < 0) || (index > size()))
            return false;
        
        if (pair == null)
            return false;
        
        String string = pair.getString();
        
        if (containsString(string))
            return false;
        
        Object object = pair.getObject();
        
        if (containsObject(object))
            return false;
        
        stringToObjectMap.put(string, object);
        objectToStringMap.put(object, string);
        pairList.add(index, pair);
        
        return true;
    }
    
    
    /**
     * <P>Adds an array of string-object pairs to the map.</P>
     *
     * @param array the array of string-object pairs
     * @see #addPairs(StringObjectPair[])
     */
    public void addPairs(Object[][] array) {
        addPairs(StringObjectPair.toArray(array));
    }
    
    
    /**
     * <P>Adds an array of <CODE>StringObjectPair</CODE> pairs to the map.</P>
     *
     * @param pairs the array of string-object pairs
     * @see #addPairs(Object[][])
     */
    public void addPairs(StringObjectPair[] pairs) {
        if (pairs == null)
            return;
        
        int length = pairs.length;
        
        for (int i = 0; i < length; i++)
            addPair(pairs[i]);
    }
    
    
    /**
     * <P>Clears the map and adds an array of string-object pairs to the
     * map.</P>
     *
     * <P>If array is <CODE>null</CODE>, then only clears.</P>
     *
     * @param array the array of string-object pairs
     */
    public void setPairs(Object[][] array) {
        clear();
        addPairs(array);
    }
    
    
    /**
     * <P>Clears the map and adds an array of <CODE>StringObjectPair</CODE>
     * pairs to the map.</P>
     *
     * <P>If pairs is <CODE>null</CODE>, then only clears.</P>
     *
     * @param pairs the array of string-object pairs
     */
    public void setPairs(StringObjectPair[] pairs) {
        clear();
        addPairs(pairs);
    }
    
    
    /**
     * <P>Removes and returns the pair corresponding to the given
     * index in order of entry in the map provided that the index
     * is in bounds; otherwise returns <CODE>null</CODE>.</P>
     *
     * @param  index the index of the pair
     * @return the pair at the given index or null
     * @see #removeByString(String)
     * @see #removeByObject(Object)
     */
    public StringObjectPair remove(int index) {
        if ((index < 0) || (index >= size()))
            return null;
        
        StringObjectPair pair = getPair(index);
        String string = pair.getString();
        Object object = pair.getObject();
        
        stringToObjectMap.remove(string);
        objectToStringMap.remove(object);
        pairList.remove(index);
        
        return pair;
    }
    
    
    /**
     * <P>Removes and returns a pair corresponding to the given
     * <CODE>String</CODE> string provided that the string is in
     * the map; otherwise returns <CODE>null</CODE>.</P>
     *
     * @param  string the string association to remove
     * @return a pair equal to the one associated to the string or null
     * @see #remove(int)
     * @see #removeByObject(Object)
     */
    public StringObjectPair removeByString(String string) {
        if (! containsString(string))
            return null;
        
        Object object = getObject(string);
        
        StringObjectPair pair = new StringObjectPair(string, object);
        
        stringToObjectMap.remove(string);
        objectToStringMap.remove(object);
        pairList.remove(pair);
        
        return pair;
    }
    
    
    /**
     * <P>Removes and returns a pair corresponding to the given
     * <CODE>Object</CODE> object provided that the object is in
     * the map; otherwise returns <CODE>null</CODE>.</P>
     *
     * @param  object the object association to remove
     * @return a pair equal to the one associated to the object or null
     * @see #remove(int)
     * @see #removeByString(String)
     */
    public StringObjectPair removeByObject(Object object) {
        if (! containsObject(object))
            return null;
        
        String string = getString(object);
        
        StringObjectPair pair = new StringObjectPair(string, object);
        
        stringToObjectMap.remove(string);
        objectToStringMap.remove(object);
        pairList.remove(pair);
        
        return pair;
    }
    
    
    /**
     * Clear the map of all strings and objects.
     */
    public void clear() {
        stringToObjectMap.clear();
        objectToStringMap.clear();
        pairList.clear();
    }
    
    
    /**
     * Returns the number of string-object pairs in this map.
     *
     * @return the number of string-object pairs
     */
    public int size() {
        return pairList.size();
    }
    
    
    /**
     * <P>Returns the <CODE>String</CODE> associated with the given
     * <CODE>Object</CODE> or <CODE>null</CODE> if no association
     * is found.</P>
     *
     * @param  object the object to associate with a string
     * @return the associated string or null
     * @see #getObject(String)
     * @see #getPair(int)
     * @see #getString(int)
     * @see #getObject(int)
     */
    public String getString(Object object) {
        if (object == null)
            return null;
        
        return (String) objectToStringMap.get(object);
    }
    
    
    /**
     * <P>Returns the <CODE>Object</CODE> associated with the given
     * <CODE>String</CODE> or <CODE>null</CODE> if no association
     * is found.</P>
     *
     * @param  string the string to associate with an object
     * @return the associated object or null
     * @see #getString(Object)
     * @see #getPair(int)
     * @see #getString(int)
     * @see #getObject(int)
     */
    public Object getObject(String string) {
        if (string == null)
            return null;
        
        return stringToObjectMap.get(string);
    }
    
    
    /**
     * <P>Returns the <CODE>StringObjectPair</CODE> pair at the given
     * index in order of entry into the map; or <CODE>null</CODE> if
     * the index is out of bounds.</P>
     *
     * <P>If the index is in bounds, this method is equivalent to
     * <CODE>getStringObjectPairs()[index]</CODE> but is more
     * efficient since no array is constructed.</P>
     * 
     * @param  the index of the pair
     * @return the associated pair or null
     * @see #getString(Object)
     * @see #getObject(String)
     * @see #getString(int)
     * @see #getObject(int)
     */
    public StringObjectPair getPair(int index) {
        if ((index < 0) || (index >= size()))
            return null;
        
        return (StringObjectPair) pairList.get(index);
    }
    
    
    /**
     * <P>Returns the <CODE>String</CODE> at the given index in order of
     * entry into the map; or <CODE>null</CODE> if the index is out of
     * bounds.</P>
     *
     * <P>If the index is in bounds, this method is equivalent to
     * <CODE>getStrings()[index]</CODE> but is more efficient since no
     * array is constructed.</P>
     * 
     * @param  the index of the string
     * @return the associated string or null
     * @see #getString(Object)
     * @see #getObject(String)
     * @see #getPair(int)
     * @see #getObject(int)
     */
    public String getString(int index) {
        StringObjectPair pair = getPair(index);
        
        if (pair == null)
            return null;
        
        return pair.getString();
    }
    
    
    /**
     * <P>Returns the <CODE>Object</CODE> at the given index in order of
     * entry into the map; or <CODE>null</CODE> if the index is out of
     * bounds.</P>
     *
     * <P>If the index is in bounds, this method is equivalent to
     * <CODE>getObjects()[index]</CODE> but is more efficient since no
     * array is constructed.</P>
     * 
     * @param  the index of the object
     * @return the associated object or null
     * @see #getString(Object)
     * @see #getObject(String)
     * @see #getPair(int)
     * @see #getString(int)
     */
    public Object getObject(int index) {
        StringObjectPair pair = getPair(index);
        
        if (pair == null)
            return null;
        
        return pair.getObject();
    }
    
    
    /**
     * <P>Returns as an array the <CODE>StringObjectPair</CODE> pairs
     * in this map in order of entry into the map.</P>
     *
     * @return the array of string-object pairs in this map
     * @see #getStrings()
     * @see #getObjects()
     * @see #getSortedStringObjectPairs()
     * @see #getSortedStrings()
     * @see #getSortedObjects()
     */
    public StringObjectPair[] getStringObjectPairs() {
        return (StringObjectPair[]) pairList.toArray(new StringObjectPair[0]);
    }
    
    
    /**
     * <P>Returns as an array the <CODE>String</CODE>s in this map
     * in order of entry into the map.</P>
     *
     * @return the array of strings in this map
     * @see #getStringObjectPairs()
     * @see #getObjects()
     * @see #getSortedStringObjectPairs()
     * @see #getSortedStrings()
     * @see #getSortedObjects()
     */
    public String[] getStrings() {
        return StringObjectPair.getStrings(getStringObjectPairs());
    }
    
    
    /**
     * <P>Returns as an array the <CODE>Object</CODE>s in this map
     * in order of entry into the map.</P>
     *
     * @return the array of objects in this map
     * @see #getStringObjectPairs()
     * @see #getStrings()
     * @see #getSortedStringObjectPairs()
     * @see #getSortedStrings()
     * @see #getSortedObjects()
     */
    public Object[] getObjects() {
        return StringObjectPair.getObjects(getStringObjectPairs());
    }
    
    
    /**
     * <P>Returns as an array the <CODE>StringObjectPair</CODE> pairs in
     * this map sorted in order of their corresponding strings order.</P>
     *
     * @return the array of sorted string-object pairs in this map
     * @see #getStringObjectPairs()
     * @see #getStrings()
     * @see #getObjects()
     * @see #getSortedStrings()
     * @see #getSortedObjects()
     */
    public StringObjectPair[] getSortedStringObjectPairs() {
        String[] strings = getSortedStrings();
        
        if (strings == null)
            return null;
        
        int length = strings.length;
        
        StringObjectPair[] pairs = new StringObjectPair[length];
        
        for (int i = 0; i < length; i++)
            pairs[i] = new StringObjectPair(strings[i], getObject(strings[i]));
        
        return pairs;
    }
    
    
    /**
     * <P>Returns as an array the <CODE>String</CODE>s in this map
     * in sorted order.</P>
     *
     * @return the sorted array of strings in this map
     * @see #getStringObjectPairs()
     * @see #getStrings()
     * @see #getObjects()
     * @see #getSortedStringObjectPairs()
     * @see #getSortedObjects()
     */
    public String[] getSortedStrings() {
        return (String[]) stringToObjectMap.keySet().toArray(new String[0]);
    }
    
    
    /**
     * <P>Returns as an array the <CODE>Object</CODE>s in this map
     * sorted in order of their corresponding strings order.</P>
     *
     * @return the sorted array of strings in this map
     * @see #getStringObjectPairs()
     * @see #getStrings()
     * @see #getObjects()
     * @see #getSortedStringObjectPairs()
     * @see #getSortedStrings()
     */
    public Object[] getSortedObjects() {
        String[] strings = getSortedStrings();
        
        if (strings == null)
            return null;
        
        int length = strings.length;
        
        Object[] objects = new Object[length];
        
        for (int i = 0; i < length; i++)
            objects[i] = getObject(strings[i]);
        
        return objects;
    }
    
    
    /**
     * Returns <CODE>true</CODE> if this map contains the <CODE>String</CODE>.
     *
     * @param  string the string to test
     * @return whether or not the string is in the map
     * @see #containsObject(Object)
     */
    public boolean containsString(String string) {
        if (string == null)
            return false;
        
        return stringToObjectMap.containsKey(string);
    }
    
    
    /**
     * Returns <CODE>true</CODE> if this map contains the <CODE>Object</CODE>.
     *
     * @param  object the object to test
     * @return whether or not the object is in the map
     * @see #containsString(String)
     */
    public boolean containsObject(Object object) {
        if (object == null)
            return false;
        
        return objectToStringMap.containsKey(object);
    }
    
}
