package com.dwave.util; /* * ObjectMap * * Copyright (C) 2001 D-Wave Systems Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ import java.util.Vector; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; /** * A Hashtable and Vector like data structure for * Objects. Each object is associated with a key, with the * usual Hashtable put/get methods, but some Vector-like * accessors have been included (eg: size(), indexOf(), elementAt()). Ideally, * this class will have all the best characteristics of Hashtables * and Vectors. * * @author Michael Coury * @version 1.03, 05/16/2001 */ public class ObjectMap implements java.io.Serializable { private static final long serialVersionUID = -7885802476177462599L; private MapItem[] hashArray; private final MapItem nonItem; private int arraySize, itemCount; private float loadFactor; //////////////////////// ///// CONSTRUCTORS ///// //////////////////////// /** * Constructs a new, empty map with a default capacity and load * factor, which is 0.65. */ public ObjectMap() { this(11, 0.65f); } /** * Constructs a new, empty map with the specified initial capacity * and default load factor, which is 0.65. * @param size the initial capacity of the HashMap. * @throws IllegalArgumentException if the initial capacity is less * than zero. */ public ObjectMap(int size) { this(size, 0.65f); } /** * Constructs a new, empty map with the specified initial * capacity and the specified load factor. * @param size the initial capacity of the HashMap. * @param loadFactor the load factor of the HashMap * @throws IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive. */ public ObjectMap(int size, float loadFactor) { if (size < 0) throw new IllegalArgumentException("Illegal Initial Capacity: "+ size); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load factor: "+ loadFactor); if (size==0) size = 1; arraySize = size; itemCount = 0; this.loadFactor = loadFactor; hashArray = new MapItem[arraySize]; nonItem = new MapItem(new Object(), new Object()); nonItem.itemHash = -1; } /** * Constructs a new map from a Hashtable * @param aTable the hashtable to use to create the map from */ public ObjectMap(Hashtable aTable) { this((int)(aTable.size()/0.65f), 0.65f); Iterator i = aTable.entrySet().iterator(); while (i.hasNext()) { Map.Entry e = (Map.Entry) i.next(); put(e.getKey(), e.getValue()); } } /** * Constructs a new map from a Vector * @param aVector the vector to use to create the map from */ public ObjectMap(Vector aVector) { this((int)(aVector.size()/0.65f), 0.65f); int vs = aVector.size(); for(int i = 0; i < vs; i++) this.addElement(aVector.elementAt(i)); } ///////////////////////////// ///// HASHTABLE METHODS ///// ///////////////////////////// /** * Associates the specified Object with the specified key * in this map. If the map previously contained a mapping for this key, * the old value is replaced. * * @param aKey key with which the specified value is to be associated. * @param aValue the object to be associated with the specified key. * @return success */ public void put(Object aKey, Object aValue) { _put(new MapItem(aKey, aValue)); itemCount++; } /** * Removes the mapping for this key if present. * @param aKey key whose object is to be removed. * @return success */ public Object remove(Object aKey) { int hashVal = _hashFunc(aKey.hashCode()); int keyHash = aKey.hashCode(); while(hashArray[hashVal] != null) { if(hashArray[hashVal].itemHash == keyHash) { MapItem temp = hashArray[hashVal]; hashArray[hashVal] = nonItem; itemCount--; if(arraySize > itemCount*3) _condense(); return temp.val; } hashVal++; hashVal %= arraySize; } return null; } /** * Returns the value to which this map maps the specified key. Returns * null if the map contains no mapping for this key. A return * value of null does not necessarily indicate that the * map contains no mapping for the key; it's also possible that the map * explicitly maps the key to null. The containsKey * operation may be used to distinguish these two cases. * @param aKey key whose associated value is to be returned. * @return the value to which this map maps the specified key. */ public Object get(Object aKey) { int hashVal = _hashFunc(aKey.hashCode()); int keyHash = aKey.hashCode(); while(hashArray[hashVal] != null) { if(hashArray[hashVal].itemHash == keyHash) return hashArray[hashVal].val; hashVal++; hashVal %= arraySize; } return null; } /** * Returns true if this map contains a mapping for the specified * key. * @return true if this map contains a mapping for the specified key. * @param aKey key whose presence in this Map is to be tested. */ public boolean containsKey(Object aKey) { int hashVal = _hashFunc(aKey.hashCode()); int keyHash = aKey.hashCode(); while(hashArray[hashVal] != null) { if(hashArray[hashVal].itemHash == keyHash) return true; hashVal++; hashVal %= arraySize; } return false; } /** * Searches for the occurrence of the requested key * @param aKey object to search for. * @return the index of the argument in this map. */ public int indexOf(Object aKey) { int hashVal = _hashFunc(aKey.hashCode()); int keyHash = aKey.hashCode(); while(hashArray[hashVal] != null) { if(hashArray[hashVal].itemHash == keyHash) return hashVal; hashVal++; hashVal %= arraySize; } return -1; } /** * Searches for the occurrence of the requested key * @param aKey object to search for. * @return the index of the argument in this map. */ public int indexOfKey(Object aKey) { return indexOf(aKey); } ////////////////////////// ///// VECTOR METHODS ///// ////////////////////////// /** * Adds the specified Object to the map with its toString() as the key. * The capacity is increased if its size becomes greater than its capacity * and load factor. * @param aValue the component to be added. */ public void addElement(Object aValue) { _put(new MapItem(aValue.toString(), aValue)); itemCount++; } /** * Removes the first (lowest-indexed) occurrence of the argument * from this map. * @param aValue the component to be removed. * @exception NullPointerException if the value is null. */ public void removeElement(Object aValue) { if(aValue == null) throw new NullPointerException(); for(int i = 0; i < arraySize; i++) { if(hashArray[i] != null) if(hashArray[i].val.equals(aValue)) hashArray[i] = nonItem; } } /** * Tests if some key maps into the specified value in this map. * This operation is more expensive than the containsKey * method. * @param aValue a value to search for. * @return true if and only if some key maps to the * aValue argument in this hashtable as * determined by the equals method; * false otherwise. * @exception NullPointerException if the value is null. */ public boolean contains(Object aValue) { if(aValue == null) throw new NullPointerException(); for(int i = 0; i < arraySize; i++) { if(hashArray[i] != null) if(hashArray[i].val.equals(aValue)) return true; } return false; } /** * Returns true if this ObjectMap maps one or more keys to this value.

* * Note that this method is identical in functionality to contains * * @param aValue value whose presence in this Hashtable is to be tested. * @return true if this map maps one or more keys to the * specified value. */ public boolean containsValue(Object aValue) { return contains(aValue); } /** * Searches for the first occurence of the given argument, testing * for equality using the equals method. * @param aValue object to search for. * @return the index of the first occurrence of the argument in this * map. * @exception NullPointerException if the value is null. */ public int indexOfValue(Object aValue) { if(aValue == null) throw new NullPointerException(); for(int i = 0; i < arraySize; i++) { if(hashArray[i] != null) if(hashArray[i].val.equals(aValue)) return i; } return -1; } /** * Deletes the component at the specified index. * The index must be a value greater than or equal to 0 * and less than the current size of the map.

* @param index the index of the object to remove. * @exception ArrayIndexOutOfBoundsException if the index was invalid. */ public boolean removeElementAt(int index) { if(index >= arraySize) throw new ArrayIndexOutOfBoundsException(index + " >= " + arraySize); if(index < 0) throw new ArrayIndexOutOfBoundsException(index + " < 0"); try { hashArray[index] = nonItem; return true; } catch(NullPointerException npe) { return false; } } /** * Returns the component at the specified index. * @param index an index into this vector. * @return the component at the specified index. * @exception ArrayIndexOutOfBoundsException if the index * is negative or not less than the current size */ public Object elementAt(int index) throws ArrayIndexOutOfBoundsException { try { return hashArray[index].val; } catch(NullPointerException npe) { return null; } } ///////////////////////// ///// OTHER METHODS ///// ///////////////////////// /** * Sets the load factor * @param newLoad the load factor of the ObjectMap * @throws IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive. */ public void setLoadFactor(float newLoad) { if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load factor: "+ loadFactor); loadFactor = newLoad; if((double)itemCount/arraySize >= loadFactor) _rehash(); } /** * Returns the current load factor of the ObjectMap * @return the load factor */ public float getLoadFactor() { return loadFactor; } /** * Tests if this ObjectMap maps no keys to values. * @return true if this hashtable maps no keys to values; * false otherwise. */ public boolean isEmpty() { return itemCount == 0; } /** * Returns all the keys as a string array * @return all the keys as a string array */ public Object[] getKeys() { Object[] keys = new Object[itemCount]; for(int i = 0; i < arraySize; i++) if(hashArray[i] != null && hashArray[i] != nonItem) keys[i] = hashArray[i].key; return keys; } /** * Removes all objects and keys from this map */ public void removeAll() { hashArray = new MapItem[arraySize]; } /** * Returns the number of components in this map. * @return the number of components in this map. */ public int size() { return itemCount; } /** * Returns the current capacity of this map (not the number of items). * @return the current capacity (the length of its internal * data array) of this map */ public int capacity() { return arraySize; } /** * Returns a string representation of this map, containing * the String representation of each item. */ public String toString() { String s = ""; for(int i = 0; i < arraySize; i++) if(hashArray[i] != null && hashArray[i] != nonItem) s = s + hashArray[i].toString()+", "; else if(hashArray[i] != null && hashArray[i] == nonItem) s = s + "(empty), "; else s = s + "(null), "; return s; } /** * Get all items as a vector * @return a vector containing all non-null items in the map */ public Vector getAsVector() { Vector v = new Vector(itemCount); for(int i = 0; i < arraySize; i++) if(hashArray[i] != null && hashArray[i] != nonItem) v.addElement(hashArray[i].val); return v; } /** * Get all items in the map as a Hashtable with a load factor of * 0.65F * @return a hashtable containing all non-null items in the map */ public Hashtable getAsHashtable() { return getAsHashtable(0.65f); } /** * Get all items in the map as a Hashtable with the load factor * specified. * @return a hashtable containing all non-null items in the map */ public Hashtable getAsHashtable(float loadFactor) { Hashtable h = new Hashtable((int)(itemCount/loadFactor), loadFactor); for(int i = 0; i < arraySize; i++) if(hashArray[i] != null && hashArray[i] != nonItem) h.put(hashArray[i].key, hashArray[i].val); return h; } /** * */ private int _hashFunc(int key) { return key % arraySize; } /** * Rehashes the contents of this map with a larger capacity. This method is * called automatically when the number of keys in this map exceeds * its capacity and load factor. */ private void _rehash() { MapItem[] oldArray = hashArray; hashArray = new MapItem[arraySize*2+1]; arraySize = hashArray.length; for(int i = 0; i < oldArray.length; i++) if(oldArray[i] != null && oldArray[i] != nonItem) _put(oldArray[i]); } /** * Rehashes the contents of this map with a smaller capacity. This method is * called automatically when the array size is more than 3 times the item * count. */ private void _condense() { arraySize = itemCount*3; MapItem[] oldArray = hashArray; hashArray = new MapItem[arraySize]; for(int i = 0; i < oldArray.length; i++) if(oldArray[i] != null && oldArray[i] != nonItem) _put(oldArray[i]); } /** * */ private void _put(MapItem item) { if((double)itemCount/arraySize >= loadFactor) _rehash(); int key = item.itemHash; int hashVal = _hashFunc(key); while(hashArray[hashVal] != null && hashArray[hashVal].itemHash != -1) { hashVal++; hashVal %= arraySize; } hashArray[hashVal] = item; } /** * */ class MapItem implements java.io.Serializable { public int itemHash; public Object key, val; public MapItem(Object aKey, Object aVal) { key = aKey; val = aVal; itemHash = key.hashCode(); } public String toString() { return "("+key+","+val+")"; } } }