580 lines
21 KiB
Java
580 lines
21 KiB
Java
/*
|
|
* 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.
|
|
* <p><b>Warning:</b> 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.
|
|
* <p>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 <code>true</code> 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 <code>null</code> 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.
|
|
* <p>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 <code>null</code> 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 <code>null</code> 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// |