Mega Code Archive

 
Categories / Java Tutorial / Collections
 

A fixed size map implementation

/*  * Licensed to the Apache Software Foundation (ASF) under one or more  * contributor license agreements.  See the NOTICE file distributed with  * this work for additional information regarding copyright ownership.  * The ASF licenses this file to You 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.  */ import java.io.Serializable; import java.util.AbstractList; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /**  * A fixed size map implementation. Holds an array of keys and array of values which correspond by  * index. Null key entries are available for use. This means that null is not a valid key.  *   * @author Jonathan Locke  * @param <K>  *            Key type  * @param <V>  *            Value type  */ public class MiniMap<K, V> implements Map<K, V>, Serializable {   private static final long serialVersionUID = 1L;   /** The array of keys. Keys that are null are not used. */   private final K[] keys;   /** The array of values which correspond by index with the keys array. */   private final V[] values;   /** The number of valid entries */   private int size;   /** The last search index. This makes putting and getting more efficient. */   private int lastSearchIndex;   /**    * Constructor    *     * @param maxEntries    *            The maximum number of entries this map can hold    */   @SuppressWarnings("unchecked")   public MiniMap(final int maxEntries)   {     keys = (K[])new Object[maxEntries];     values = (V[])new Object[maxEntries];   }   /**    * Constructor    *     * @param map    *            The map    * @param maxEntries    *            The maximum number of entries this map can hold    */   public MiniMap(final Map<? extends K, ? extends V> map, final int maxEntries)   {     this(maxEntries);     putAll(map);   }   /**    * @return True if this MicroMap is full    */   public boolean isFull()   {     return size == keys.length;   }   /**    * @see java.util.Map#size()    */   public int size()   {     return size;   }   /**    * @see java.util.Map#isEmpty()    */   public boolean isEmpty()   {     return size == 0;   }   /**    * @see java.util.Map#containsKey(java.lang.Object)    */   public boolean containsKey(final Object key)   {     return findKey(0, key) != -1;   }   /**    * @see java.util.Map#containsValue(java.lang.Object)    */   public boolean containsValue(final Object value)   {     return findValue(0, value) != -1;   }   /**    * @see java.util.Map#get(java.lang.Object)    */   public V get(final Object key)   {     // Search for key     final int index = findKey(key);     if (index != -1)     {       // Return value       return values[index];     }     // Failed to find key     return null;   }   /**    * @see java.util.Map#put(java.lang.Object, java.lang.Object)    */   public V put(final K key, final V value)   {     // Search for key     final int index = findKey(key);     if (index != -1)     {       // Replace existing value       final V oldValue = values[index];       values[index] = value;       return oldValue;     }     // Is there room for a new entry?     if (size < keys.length)     {       // Store at first null index and continue searching after null index       // next time       final int nullIndex = nextNullKey(lastSearchIndex);       lastSearchIndex = nextIndex(nullIndex);       keys[nullIndex] = key;       values[nullIndex] = value;       size++;       return null;     }     else     {       throw new IllegalStateException("Map full");     }   }   /**    * @see java.util.Map#remove(java.lang.Object)    */   public V remove(final Object key)   {     // Search for key     final int index = findKey(key);     if (index != -1)     {       // Store value       final V oldValue = values[index];       keys[index] = null;       values[index] = null;       size--;       return oldValue;     }     return null;   }   /**    * @see java.util.Map#putAll(java.util.Map)    */   public void putAll(Map<? extends K, ? extends V> map)   {     for (final Iterator<? extends Entry<? extends K, ? extends V>> iterator = map.entrySet()       .iterator(); iterator.hasNext();)     {       final Map.Entry<? extends K, ? extends V> e = iterator.next();       put(e.getKey(), e.getValue());     }   }   /**    * @see java.util.Map#clear()    */   public void clear()   {     for (int i = 0; i < keys.length; i++)     {       keys[i] = null;       values[i] = null;     }     size = 0;   }   /**    * @see java.util.Map#keySet()    */   public Set<K> keySet()   {     return new AbstractSet<K>()     {       @Override       public Iterator<K> iterator()       {         return new Iterator<K>()         {           public boolean hasNext()           {             return i < size - 1;           }           public K next()           {             // Just in case... (WICKET-428)             if (!hasNext())             {               throw new NoSuchElementException();             }             // Find next key             i = nextKey(nextIndex(i));             // Get key             return keys[i];           }           public void remove()           {             keys[i] = null;             values[i] = null;             size--;           }           int i = -1;         };       }       @Override       public int size()       {         return size;       }     };   }   /**    * @see java.util.Map#values()    */   public Collection<V> values()   {     return new AbstractList<V>()     {       @Override       public V get(final int index)       {         if (index > size - 1)         {           throw new IndexOutOfBoundsException();         }         int keyIndex = nextKey(0);         for (int i = 0; i < index; i++)         {           keyIndex = nextKey(keyIndex + 1);         }         return values[keyIndex];       }       @Override       public int size()       {         return size;       }     };   }   /**    * @see java.util.Map#entrySet()    */   public Set<Entry<K, V>> entrySet()   {     return new AbstractSet<Entry<K, V>>()     {       @Override       public Iterator<Entry<K, V>> iterator()       {         return new Iterator<Entry<K, V>>()         {           public boolean hasNext()           {             return index < size;           }           public Entry<K, V> next()           {             if (!hasNext())             {               throw new NoSuchElementException();             }             keyIndex = nextKey(nextIndex(keyIndex));             index++;             return new Map.Entry<K, V>()             {               public K getKey()               {                 return keys[keyIndex];               }               public V getValue()               {                 return values[keyIndex];               }               public V setValue(final V value)               {                 final V oldValue = values[keyIndex];                 values[keyIndex] = value;                 return oldValue;               }             };           }           public void remove()           {             keys[keyIndex] = null;             values[keyIndex] = null;           }           int keyIndex = -1;           int index = 0;         };       }       @Override       public int size()       {         return size;       }     };   }   /**    * Computes the next index in the key or value array (both are the same length)    *     * @param index    *            The index    * @return The next index, taking into account wraparound    */   private int nextIndex(final int index)   {     return (index + 1) % keys.length;   }   /**    * Finds the index of the next non-null key. If the map is empty, -1 will be returned.    *     * @param start    *            Index to start at    * @return Index of next non-null key    */   private int nextKey(final int start)   {     int i = start;     do     {       if (keys[i] != null)       {         return i;       }       i = nextIndex(i);     }     while (i != start);     return -1;   }   /**    * Finds the index of the next null key. If no null key can be found, the map is full and -1    * will be returned.    *     * @param start    *            Index to start at    * @return Index of next null key    */   private int nextNullKey(final int start)   {     int i = start;     do     {       if (keys[i] == null)       {         return i;       }       i = nextIndex(i);     }     while (i != start);     return -1;   }   /**    * Finds a key by starting at lastSearchIndex and searching from there. If the key is found,    * lastSearchIndex is advanced so the next key search can find the next key in the array, which    * is the most likely to be retrieved.    *     * @param key    *            Key to find in map    * @return Index of matching key or -1 if not found    */   private int findKey(final Object key)   {     if (size > 0)     {       // Find key starting at search index       final int index = findKey(lastSearchIndex, key);       // Found match?       if (index != -1)       {         // Start search at the next index next time         lastSearchIndex = nextIndex(index);         // Return index of key         return index;       }     }     return -1;   }   /**    * Searches for a key from a given starting index.    *     * @param key    *            The key to find in this map    * @param start    *            Index to start at    * @return Index of matching key or -1 if not found    */   private int findKey(final int start, final Object key)   {     int i = start;     do     {       if (key.equals(keys[i]))       {         return i;       }       i = nextIndex(i);     }     while (i != start);     return -1;   }   /**    * Searches for a value from a given starting index.    *     * @param start    *            Index to start at    * @param value    *            The value to find in this map    * @return Index of matching value or -1 if not found    */   private int findValue(final int start, final Object value)   {     int i = start;     do     {       if (value.equals(values[i]))       {         return i;       }       i = nextIndex(i);     }     while (i != start);     return -1;   } }