001    package edu.harvard.deas.hyperenc.util;
002    
003    import java.util.SortedMap;
004    import java.util.TreeMap;
005    
006    import org.apache.log4j.Logger;
007    
008    /**
009     * A collection of objects and associated integer keys, with support for
010     * nearest-neighbor lookups. In other words, this is a {@link java.util.Map}
011     * with integer keys, and the <code>get(int key)</code> method retrieves the
012     * object whose key is numerically closest to <code>key</code>.
013     */
014    public class NNLookup<V>
015    {
016      private static final Logger logger = Logger.getLogger(NNLookup.class);
017      
018      private static final long serialVersionUID = 1L;
019      
020      private SortedMap<Integer,V> myMap = new TreeMap<Integer,V>();
021            
022      /**
023       * Add the object <code>object</code> using <code>key</code> as its index.
024       * 
025       * @param key
026       *        The key to associated with <code>object</code>.
027       * @param object
028       *        The object to be stored.
029       */
030      synchronized public void add(int key, V object)
031      {
032              myMap.put(key, object);
033              logger.trace("Added item " + object + " with key " + key);
034      }
035    
036      /**
037       * Gets the object using this exact key. If no object has key <i>equal</i> to
038       * <code>key</code>, returns <code>null</code>. This is the normal Map behavior.
039       * 
040       * @param key
041       *        The integer key value to search for.
042       * @return The data of the object with the <b>exact</b> key. Should be null
043       *         iff the lookup table is empty.
044       */
045      synchronized public V getExact(int key)
046      {
047        return myMap.get(key);  // use the original TreeMap get method
048      }
049    
050      /**
051       * Get an object using the key <code>key</code>. Gets the object whose key is
052       * numerically closest to the one passed in.
053       * 
054       * @param key
055       *        The integer key value to search for.
056       * @return the object with the <i>closest</i> key, or <code>null</code> iff
057       *         the lookup table is empty.
058       */
059      synchronized public V get(int key)
060      {
061        Integer k = getClosestKey(key);
062    
063        logger.trace("Attempting to get object with key " + key
064            + " -- the closest key is " + k);
065        if (k != null)
066          return myMap.get(k);
067        else
068          return null;
069      }
070    
071      /** Deletes the object with the key nearest to the specified key.
072          @param key The key value you want to delete.
073          @return The value mapped to the key deleted. */
074      synchronized public V delete(int key)
075      {
076        Integer k = getClosestKey(key);
077    
078        if (k != null)
079          return myMap.remove(k);
080        else
081          return null;
082      }
083    
084      /** Deletes the object with the specified key.
085          @param key The key value you want to delete.
086          @return The value mapped to the key deleted. */
087      synchronized public Object deleteExact(int key)
088      {
089        return myMap.remove(new Integer(key));
090      }
091    
092      /** Get the key closest to the one passed in.
093          @param key The key value to search for.
094          @return The object with the closest key.  Should be null iff
095          the lookup table is empty.  Currently this is an Integer. */
096      synchronized public Integer getClosestKey(int key)
097      {
098        if (myMap.containsKey(key)) {
099          return key;
100        } else {
101          Integer closestAbove = null;
102          Integer closestBelow = null;
103          
104          // Check keys greater than or equal to key
105          SortedMap<Integer,V> m = myMap.tailMap(key);
106          if (!m.isEmpty()) {
107            closestAbove = m.firstKey();
108          } 
109          
110          // Check keys less than or equal to key
111          m = myMap.headMap(key);
112          if (!m.isEmpty()) {
113            closestBelow = m.lastKey();
114          }
115    
116              if (closestAbove == null) {
117                      return closestBelow;
118              } else if (closestBelow == null) {
119                      return closestAbove;
120              } else if (key - closestBelow.intValue() < closestAbove.intValue() - key) {
121                      return closestBelow;
122              } else {
123                      return closestAbove;
124              }
125        }
126      }
127      
128      /**
129       * Returns the number of key-value pairs stored in this map.
130       * @return the size of this map
131       */
132      public int size() {
133        return myMap.size();
134      }
135    }
136    
137    
138    
139    
140    
141