/* * Copyright (c) 2002,2009 Declarative Engineering LLC. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Declarative Engineering LLC * verson 1 which accompanies this distribution, and is available at * http://declarativeengineering.com/legal/DE_Developer_License_v1.txt */ package com.common.util; import com.common.comparison.*; /** * A map collection that maps values by a key so that they may be looked up by that key. *

Warning: This class is NOT thread safe. To use it in a multi threaded environment all access to the map should by in a synchronization block. */ public class LiteHashMap extends Map implements IHashMap { /** A reusable always empty map. */ public static final LiteHashMap EMPTY_MAP = new LiteHashMap(1); static { EMPTY_MAP.isChangeable(false); }//static// private LiteList keys = null; /** Whether the map should maintain the counter for each map entry. If this is true then items added more than once to the map (same key/value pair) will increment the count, and removes will decrement the count. */ private boolean maintainCount = false; /** * The Entry wrappers the key/value pair. */ protected static class Entry extends BasicEntry { protected Object key = null; protected Object value = null; protected int count = 1; public Entry() { super(); }//Entry()// public void readExternal(java.io.ObjectInput in) throws java.io.IOException, ClassNotFoundException { super.readExternal(in); key = in.readObject(); value = in.readObject(); count = in.readInt(); //Ensure that null keys are valid.// if(key == null) { key = NULL_KEY; }//if// }//readExternal()// public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException { super.writeExternal(out); out.writeObject(key == NULL_KEY ? null : key); out.writeObject(value); out.writeInt(count); }//writeExternal()// public int createKeyHash() { return key.hashCode(); }//createKeyHash()// public String toString() { return super.toString() + "\r\n\tKey: " + (key == NULL_KEY ? "null" : key.toString()) + "\r\n\tValue: " + value + (next != null ? "\r\n\t" + next.toString() : ""); }//toString()// }//Entry// public static class KeyIterator extends Iterator implements IReversableIterator { public KeyIterator(LiteHashMap hashMap) { super(hashMap); }//KeyIterator()// public Object next() { Object result = ((Entry) nextEntry()).key; if(result == NULL_KEY) { result = null; }//if// return result; }//next()// public Object previous() { Object result = ((Entry) previousEntry()).key; if(result == NULL_KEY) { result = null; }//if// return result; }//previous()// }//KeyIterator// public static class ValueIterator extends Iterator implements IIterator { public ValueIterator(LiteHashMap hashMap) { super(hashMap); }//ValueIterator()// public Object next() { return ((Entry) nextEntry()).value; }//next()// public Object previous() { return ((Entry) previousEntry()).value; }//previous()// }//ValueIterator// /** * LiteHashMap constructor. *

This constructor is private because is not advisable to use the default initial size because it will not be optimal for most applications. */ public LiteHashMap() { super(); }//LiteHashMap()// /** * LiteHashMap constructor. * @param initialCapacity The initial capacity of the map. This value should be between 1..+N, a value of zero will be silently modified to a value of one. Any other value will cause an IllegalArgumentException. */ public LiteHashMap(int initialCapacity) { super(initialCapacity); }//LiteHashMap()// /** * LiteHashMap constructor. * @param initialCapacity The initial capacity of the map. This value should be between 1..+N, a value of zero will be silently modified to a value of one. Any other value will cause an IllegalArgumentException. * @param maintainCount Whether the map should maintain the counter for each map entry. If this is true then items added more than once to the map (same key/value pair) will increment the count, and removes will decrement the count. */ public LiteHashMap(int initialCapacity, boolean maintainCount) { super(initialCapacity); this.maintainCount = maintainCount; }//LiteHashMap()// /** * LiteHashMap constructor. * @param initialCapacity The initial capacity of the map. This value should be between 1..+N, a value of zero will be silently modified to a value of one. Any other value will cause an IllegalArgumentException. * @param loadFactor The ratio that determines when the map should increase capacity. This value should be greater than 0.0 and any other value will cause an IllegalArgumentException. */ public LiteHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); }//LiteHashMap()// /** * LiteHashMap constructor. * @param initialCapacity The initial capacity of the map. This value should be between 1..+N, a value of zero will be silently modified to a value of one. Any other value will cause an IllegalArgumentException. * @param loadFactor The ratio that determines when the map should increase capacity. This value should be greater than 0.0 and any other value will cause an IllegalArgumentException. * @param maintainCount Whether the map should maintain the counter for each map entry. If this is true then items added more than once to the map (same key/value pair) will increment the count, and removes will decrement the count. */ public LiteHashMap(int initialCapacity, float loadFactor, boolean maintainCount) { super(initialCapacity, loadFactor); this.maintainCount = maintainCount; }//LiteHashMap()// /** * LiteHashMap constructor. * @param initialCapacity The initial capacity of the map. This value should be between 1..+N, a value of zero will be silently modified to a value of one. Any other value will cause an IllegalArgumentException. * @param loadFactor The ratio that determines when the map should increase capacity. This value should be greater than 0.0 and any other value will cause an IllegalArgumentException. * @param keyComparator The comparator used to compare key objects. * @param valueComparator The comparator used to compare value objects. */ public LiteHashMap(int initialCapacity, float loadFactor, IComparator keyComparator, IComparator valueComparator) { super(initialCapacity, loadFactor, keyComparator, valueComparator); }//LiteHashMap()// /** * LiteHashMap constructor. * @param initialCapacity The initial capacity of the map. This value should be between 1..+N, a value of zero will be silently modified to a value of one. Any other value will cause an IllegalArgumentException. * @param loadFactor The ratio that determines when the map should increase capacity. This value should be greater than 0.0 and any other value will cause an IllegalArgumentException. * @param keyComparator The comparator used to compare key objects. * @param valueComparator The comparator used to compare value objects. * @param maintainCount Whether the map should maintain the counter for each map entry. If this is true then items added more than once to the map (same key/value pair) will increment the count, and removes will decrement the count. */ public LiteHashMap(int initialCapacity, float loadFactor, IComparator keyComparator, IComparator valueComparator, boolean maintainCount) { super(initialCapacity, loadFactor, keyComparator, valueComparator); this.maintainCount = maintainCount; }//LiteHashMap()// /** * LiteHashMap constructor. * @param initialCapacity The initial capacity of the map. This value should be between 1..+N, a value of zero will be silently modified to a value of one. Any other value will cause an IllegalArgumentException. * @param keyComparator The comparator used to compare key objects. * @param valueComparator The comparator used to compare value objects. */ public LiteHashMap(int initialCapacity, IComparator keyComparator, IComparator valueComparator) { super(initialCapacity, keyComparator, valueComparator); }//LiteHashMap()// /** * LiteHashMap constructor. * @param keyComparator The comparator used to compare key objects. * @param valueComparator The comparator used to compare value objects. */ public LiteHashMap(IComparator keyComparator, IComparator valueComparator) { super(keyComparator, valueComparator); }//LiteHashMap()// /** * LiteHashMap constructor. * This constructor will copy an existing IHashMap. * @param map A map to copy. */ public LiteHashMap(IHashMap map) { super(map.getSize() > 10 ? map.getSize() : 10, map.getLoadFactor()); IIterator iterator = map.keyIterator(); //Iterate over the existing map's keys and put the key value pairs in this map.// while(iterator.hasNext()) { Object key = iterator.next(); Object value = map.get(key); put(key, value); }//while// }//LiteHashMap()// /** * LiteHashMap constructor. * This constructor will copy an existing IHashMap. * @param map A map to copy. */ public LiteHashMap(LiteHashMap map) { super(map.getSize() > 10 ? map.getSize() : 10, map.getLoadFactor(), map.getKeyComparator(), map.getValueComparator()); IIterator iterator = map.keyIterator(); //Iterate over the existing map's keys and put the key value pairs in this map.// while(iterator.hasNext()) { Object key = iterator.next(); Object value = map.get(key); put(key, value); }//while// }//LiteHashMap()// /** * LiteHashMap constructor. * @param initialSize The initial size of the map. * @param initialKeyValuePairs The initial key/value pairs to be placed in the map. This should be an array of arrays where the inner array consists of a key object and a value object, no more, no less. */ public LiteHashMap(int initialSize, Object[][] initialKeyValuePairs) { this(initialSize); for(int index = 0; index < initialKeyValuePairs.length; index++) { if(initialKeyValuePairs[index].length == 2) { Object key = initialKeyValuePairs[index][0]; Object value = initialKeyValuePairs[index][1]; put(key, value); }//if// }//for// }//LiteHashMap()// /** * LiteHashMap constructor. * @param initialCapacity The initial capacity of the map. This value should be between 1..+N, a value of zero will be silently modified to a value of one. Any other value will cause an IllegalArgumentException. * @param loadFactor The ratio that determines when the map should increase capacity. This value should be greater than 0.0 and any other value will cause an IllegalArgumentException. * @param keyComparator The comparator used to compare key objects. * @param valueComparator The comparator used to compare value objects. * @param keys The keys, or null if the value index is used. * @param values The values, or null if the key index is used. If both values and keys are null then nothing is added to the map. * @param isChangeable Whether the map is changeable after initialization. */ public LiteHashMap(int initialSize, float loadFactor, IComparator keyComparator, IComparator valueComparator, Object[] keys, Object[] values, boolean isChangeable) { this(initialSize, loadFactor, keyComparator, valueComparator); if(keys != null && values != null) { for(int index = 0, max = Math.min(keys.length, values.length); index < max; index++) { put(keys[index], values[index]); }//for// }//if// else if(keys != null) { for(int index = 0; index < keys.length; index++) { put(keys[index], new Integer(index)); }//for// }//else if// else if(values != null) { for(int index = 0; index < values.length; index++) { put(new Integer(index), values[index]); }//for// }//else if// }//LiteHashMap()// /** * Determines whether the key exists in the map. * @param key The key to look for. * @return Will be true if the key is already in the map. */ public boolean containsKey(Object key) { return get(key) != null; }//containsKey()// /** * Creates an entry object of the specific type used by this map. * @return An entry object that can be filled and passed to the addEntry method. */ protected IBasicEntry createEntry() { return new Entry(); }//createEntry()// /** * Gets the first key in the map. * @return The first accessable key in the mapping. */ public Object getFirstKey() { if(getSize() > 0) { IBasicEntry[] entries = getEntries(); Object result = null; for(int index = 0; (result == null) && (index < entries.length); index++) { if(entries[index] != null) { result = ((Entry) entries[index]).key; }//if// }//for// return result; }//if// else { throw new RuntimeException("No keys in the mapping."); }//else// }//getFirstKey()// /** * Gets an object in the map by its' key. * @param key The key whose value should be retieved. * @return The value associated with the key. A null value will be returned only if the key was not found. */ public Object get(Object key) { //Ensure that null keys are valid.// if(key == null) { key = NULL_KEY; }//if// IBasicEntry[] entries = getEntries(); int hash = getKeyComparator().hash(key); int index = (hash & 0x7FFFFFFF) % entries.length; for(Entry entry = (Entry) entries[index]; entry != null; entry = (Entry) entry.next) { if((entry.hash == hash) && (Comparator.isEqual(getKeyComparator().compare(entry.key, key)))) { return entry.value; }//if// }//for// return null; }//get()// /** * Gets the reference count for the map entry identified by the given key. * @param key The map entry's key. * @return The number of times the map entry (same exact key/value pair) has been added to the mapping. */ public int getCount(Object key) { //Ensure that null keys are valid.// if(key == null) { key = NULL_KEY; }//if// IBasicEntry[] entries = getEntries(); int hash = getKeyComparator().hash(key); int index = (hash & 0x7FFFFFFF) % entries.length; for(Entry entry = (Entry) entries[index]; entry != null; entry = (Entry) entry.next) { if((entry.hash == hash) && (Comparator.isEqual(getKeyComparator().compare(entry.key, key)))) { return entry.count; }//if// }//for// return 0; }//getCount()// /** * Gets the key at the given index. *

NOTE: This method is intended for use by the iterators and it is not advisable to use this method else where. * @param index The index in the list of keys. * @return The key at the given index. */ public Object getKey(int index) { if(keys == null) { refreshKeys(); }//if// return keys.get(index); }//getKey()// /** * Gets the collection of all keys in the map. * @param keys A collection that will contain the keys. * @return The collection with the keys in it. This will be the same collection as the one passed if it is non-null. */ public IList getKeys(IList keys) { if(this.keys == null) { refreshKeys(); }//if// if(keys == null) { keys = new LiteList(this.keys.getSize(), 10); }//if// keys.addAll(this.keys); return keys; }//getKeys()// /** * Gets the collection of all keys in the map. * @param array An array where key values will be placed. * @return The count of keys placed in the array. */ public int getKeys(Object array) { return getKeys(array, 0); }//getKeys()// /** * Gets the collection of all keys in the map. * @param array An array where key values will be placed. * @param outputOffset The offset in the array where the first key value will be placed. * @return The count of keys placed in the array. */ public int getKeys(Object array, int outputOffset) { if(keys == null) { refreshKeys(); }//if// return keys.toArray(array, outputOffset); }//getKeys()// /** * Invalidates the collection of keys. * This should occur when a value is added/removed/changed in a way that invalidates the collection of keys. */ protected void invalidateKeys() { keys = null; }//invalidateKeys()// /** * Gets an iterator over the keys contained in this collection. * @return An iterator over the map keys. */ public IIterator keyIterator() { return new KeyIterator(this); }//keyIterator()// /** * Places a key/value pair in the map. * The value can be retrieved later with the given key. * @param key The key that will be used to map the value. * @param value The value stored in map. A null value will remove the key from the mapping. * @return The value previously associated with the key. */ public Object put(Object key, Object value) { Object result = null; //Verify that the collection can be modified.// verifyIsChangeable(); //Ensure that null keys are valid.// if(key == null) { key = NULL_KEY; }//if// if(value == null) { //Remove the key if it exists because you cannot associate a key with a null value.// result = remove(key); }//if// else { IBasicEntry[] entries = getEntries(); int hash = getKeyComparator().hash(key); int index = (hash & 0x7FFFFFFF) % entries.length; Entry entry = null; //Try to locate an existing value.// for(entry = (Entry) entries[index]; entry != null; entry = (Entry) entry.next) { if((entry.hash == hash) && (Comparator.isEqual(getKeyComparator().compare(entry.key, key)))) { if(maintainCount) { if(getValueComparator().compare(entry.value, value) == Comparator.EQUAL) { entry.count++; }//if// else { throw new RuntimeException("Cannot replace one value with another in a map when maintaining a count."); }//else// }//if// else { Object oldValue = entry.value; entry.value = value; return oldValue; }//else// }//if// }//for// //Determine if we need to re-hash.// if(resize(getSize() + 1)) { entries = getEntries(); //Recalculate the insertion index if the map resized.// index = (hash & 0x7FFFFFFF) % entries.length; }//if// //Create and add the new entry.// entry = (Entry) createEntry(); entry.hash = hash; entry.key = key; entry.value = value; addEntry(index, entry); if(keys != null) { keys.add(key); //Just add the key instead of invalidating the keys collection.// }//if// }//else// return result; }//put()// /** * Recreates the collection of keys. */ protected void refreshKeys() { IBasicEntry[] entries = getEntries(); int index; Entry entry; keys = new LiteList(getSize(), 20); for(index = 0; index < entries.length; index++) { for(entry = (Entry) entries[index]; entry != null; entry = (Entry) entry.next) { Object key = entry.key; //Ensure that null keys are valid.// if(key == NULL_KEY) { key = null; }//if// keys.add(key); }//for// }//for// }//refreshKeys()// /** * Optimizes the collection of Entry objects to improve access time. * @param arrayLength The new length of the entry array. */ protected void rehash(int arrayLength) { super.rehash(arrayLength); //Invalidate the list of keys.// invalidateKeys(); }//rehash()// /** * Removes a key/value pair from the map. * @param key The key that should be removed (with its' value) from the map. * @return Will be the value removed from map. A null value is returned if the key was not found. */ public Object remove(Object key) { //Verify that the collection can be modified.// verifyIsChangeable(); //Ensure that null keys are valid.// if(key == null) { key = NULL_KEY; }//if// IBasicEntry[] entries = getEntries(); int hash = getKeyComparator().hash(key); int index = (hash & 0x7FFFFFFF) % entries.length; Entry previousEntry = null; Entry entry = null; Object result = null; //Iterate through the entries stored at the key's hash location and remove the given key.// for(entry = (Entry) entries[index]; entry != null; entry = (Entry) entry.next) { if((entry.hash == hash) && (Comparator.isEqual(getKeyComparator().compare(entry.key, key)))) { //Save the removed value.// result = entry.value; if(--entry.count == 0) { //Remove the entry from the map.// removeEntry(index, previousEntry); //Could find the key in the collection and remove it, but that may take to long.// invalidateKeys(); }//if// //Break from the loop so we can exit.// break; }//if// previousEntry = entry; }//for// return result; }//remove()// /** * Removes all key/value pairs from the map. */ public void removeAll() { //Verify that the collection can be modified.// verifyIsChangeable(); invalidateKeys(); removeAllEntries(); }//removeAll()// /** * Gets the collection of all keys in the map. * @param keys An array of the necessary type to old the key values. * @return The collection with the keys in it. This will be the same collection as the one passed if it is non-null. */ public int toKeyArray(Object keyArray) { if(keys == null) { refreshKeys(); }//if// return keys.toArray(keyArray); }//toKeyArray()// /** * Gets an iterator over the values contained in this collection. * @return An iterator over the map values. */ public IIterator valueIterator() { return new ValueIterator(this); }//valueIterator()// }//HashMap//