/*******************************************************************************
* Copyright (c) 2009 Centrum Wiskunde en Informatica (CWI)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Arnold Lankamp - interfaces and implementation
*******************************************************************************/
package org.rascalmpl.value.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.rascalmpl.value.IValue;
import org.rascalmpl.value.impl.util.collections.ShareableValuesHashSet;
/**
* This map is similar to the ShareableHashMap, but is indexed by a value (check with isEqual).
*
* @author Arnold Lankamp
*
* @param <V>
* The value type
*/
public final class ValueIndexedHashMap<V> implements Map<IValue, V>{
private final static int INITIAL_LOG_SIZE = 4;
private int modSize;
private int hashMask;
private Entry<V>[] data;
private int threshold;
private int load;
/**
* Constructor.
*/
public ValueIndexedHashMap(){
super();
modSize = INITIAL_LOG_SIZE;
int tableSize = 1 << modSize;
hashMask = tableSize - 1;
data = (Entry<V>[]) new Entry[tableSize];
threshold = tableSize;
load = 0;
}
/**
* Copy constructor
*
* @param valueIndexedHashMap
* The map to copy.
*/
public ValueIndexedHashMap(ValueIndexedHashMap<V> valueIndexedHashMap){
super();
modSize = valueIndexedHashMap.modSize;
int tableSize = 1 << modSize;
hashMask = tableSize - 1;
data = valueIndexedHashMap.data.clone();
threshold = tableSize;
load = valueIndexedHashMap.load;
}
/**
* Removes all the entries from this map.
*/
public void clear(){
modSize = INITIAL_LOG_SIZE;
int tableSize = 1 << modSize;
hashMask = tableSize - 1;
data = (Entry<V>[]) new Entry[tableSize];
threshold = tableSize;
load = 0;
}
/**
* Rehashes this map.
*/
private void rehash(){
modSize++;
int tableSize = 1 << modSize;
hashMask = tableSize - 1;
Entry<V>[] newData = (Entry<V>[]) new Entry[tableSize];
threshold = tableSize;
Entry<V>[] oldData = data;
for(int i = oldData.length - 1; i >= 0; i--){
Entry<V> entry = oldData[i];
if(entry != null){
// Determine the last unchanged entry chain.
Entry<V> lastUnchangedEntryChain = entry;
int newLastUnchangedEntryChainIndex = entry.hash & hashMask;
Entry<V> e = entry.next;
while(e != null){
int newIndex = e.hash & hashMask;
if(newIndex != newLastUnchangedEntryChainIndex){
lastUnchangedEntryChain = e;
newLastUnchangedEntryChainIndex = newIndex;
}
e = e.next;
}
newData[newLastUnchangedEntryChainIndex] = lastUnchangedEntryChain;
// Reconstruct the other entries (if necessary).
while(entry != lastUnchangedEntryChain){
int hash = entry.hash;
int position = hash & hashMask;
newData[position] = new Entry<>(hash, entry.key, entry.value, newData[position]);
entry = entry.next;
}
}
}
data = newData;
}
/**
* Makes sure the size of the entry array and the load of the map stay in proper relation to
* eachother.
*/
private void ensureCapacity(){
if(load > threshold){
rehash();
}
}
/**
* Replaces the value in the entry by the given value.
*
* @param position
* The position in the entry array where the entry is located.
* @param entry
* The entry in which the value must be replaced.
* @param newValue
* The value.
*/
private void replaceValue(int position, Entry<V> entry, V newValue){
Entry<V> e = data[position];
// Reconstruct the updated entry.
data[position] = new Entry<>(entry.hash, entry.key, newValue, entry.next);
// Reconstruct the other entries (if necessary).
while(e != entry){
data[position] = new Entry<>(e.hash, e.key, e.value, data[position]);
e = e.next;
}
}
/**
* Inserts the given key-value pair into this map. In case there already is a value associated
* with the given key, the value will be updated and the previous value returned.
*
* @param key
* The key
* @param value
* The value
* @return The previous value that was associated with the key (if any); null otherwise.
*/
public V put(IValue key, V value){
ensureCapacity();
int hash = key.hashCode();
int position = hash & hashMask;
Entry<V> currentStartEntry = data[position];
// Check if the key is already in here.
if(currentStartEntry != null){
Entry<V> entry = currentStartEntry;
do{
if(hash == entry.hash && entry.key.isEqual(key)){ // Replace if present.
replaceValue(position, entry, value);
return entry.value; // Return the old value.
}
entry = entry.next;
}while(entry != null);
}
data[position] = new Entry<>(hash, key, value, currentStartEntry); // Insert the new entry.
load++;
return null;
}
/**
* Removes the entry from this map that is identified by the given key (if present).
*
* @param key
* The key that identifies the entry to remove.
* @return The value that was associated with the given key; null if the key was not present in
* the map.
*/
public V remove(Object object){
IValue key = (IValue) object;
int hash = key.hashCode();
int position = hash & hashMask;
Entry<V> currentStartEntry = data[position];
if(currentStartEntry != null){
Entry<V> entry = currentStartEntry;
do{
if(hash == entry.hash && entry.key.isEqual(key)){
Entry<V> e = data[position];
data[position] = entry.next;
// Reconstruct the other entries (if necessary).
while(e != entry){
data[position] = new Entry<>(e.hash, e.key, e.value, data[position]);
e = e.next;
}
load--;
return entry.value; // Return the value.
}
entry = entry.next;
}while(entry != null);
}
return null; // Not found.
}
/**
* Retrieves the value from the entry in this map which is identified by the given key
* (if present).
*
* @param key
* The key that identifies the entry that contains the value.
* @return The retrieved value; null if not present.
*/
public V get(Object object){
IValue key = (IValue) object;
int hash = key.hashCode();
int position = hash & hashMask;
Entry<V> entry = data[position];
while(entry != null){
if(hash == entry.hash && key.isEqual(entry.key)) return entry.value;
entry = entry.next;
}
return null;
}
/**
* Checks if there is an entry present in this map, which is identified by the given key.
*
* @param key
* The key that identifies the entry.
* @return True if this map contains an entry which is identified by the given key;
* false otherwise.
*/
public boolean contains(IValue key){
return (get(key) != null);
}
/**
* Returns the number of entries this map contains.
*
* @return The number of entries this map contains.
*/
public int size(){
return load;
}
/**
* Checks whether or not this map is empty.
*
* @return True if this map was empty; false otherwise.
*/
public boolean isEmpty(){
return (load == 0);
}
/**
* Constructs an iterator for the entries in this map.
*
* @return An iterator for the entries in this map.
*/
public Iterator<Map.Entry<IValue, V>> entryIterator(){
return new EntryIterator<>(data);
}
/**
* Constructs an iterator for the keys in this map.
*
* @return An iterator for the keys in this map.
*/
public Iterator<IValue> keysIterator(){
return new KeysIterator<>(data);
}
/**
* Constructs an iterator for the values in this map.
*
* @return An iterator for the values in this map.
*/
public Iterator<V> valuesIterator(){
return new ValuesIterator<>(data);
}
/**
* Copies over all entries from the given map, to this map.
*/
public void putAll(Map<? extends IValue, ? extends V> otherMap){
Set<Map.Entry<IValue, V>> entrySet = (Set<Map.Entry<IValue, V>>) (Set<?>) otherMap.entrySet(); // Generics stink.
Iterator<Map.Entry<IValue, V>> entrySetIterator = entrySet.iterator();
while(entrySetIterator.hasNext()){
Map.Entry<IValue, V> next = entrySetIterator.next();
put(next.getKey(), next.getValue());
}
}
/**
* Checks if this map contains an entry with the given key.
*/
public boolean containsKey(Object object){
IValue key = (IValue) object;
int hash = key.hashCode();
int position = hash & hashMask;
Entry<V> entry = data[position];
while(entry != null){
if(hash == entry.hash && key.isEqual(entry.key)) return true;
entry = entry.next;
}
return false;
}
/**
* Checks if this map contains an entry with the given value.
*/
public boolean containsValue(Object value){
Iterator<V> valuesIterator = valuesIterator();
while(valuesIterator.hasNext()){
V nextValue = valuesIterator.next();
if(nextValue == value || (nextValue != null && nextValue.equals(value))){
return true;
}
}
return false;
}
/**
* Constructs a set containing all entries from this map.
*/
public Set<Map.Entry<IValue, V>> entrySet(){
ShareableHashSet<Map.Entry<IValue, V>> entrySet = new ShareableHashSet<>();
Iterator<Map.Entry<IValue, V>> entriesIterator = entryIterator();
while(entriesIterator.hasNext()){
entrySet.add(entriesIterator.next());
}
return entrySet;
}
/**
* Constructs a set containing all keys from this map.
*/
public Set<IValue> keySet(){
ShareableValuesHashSet keysSet = new ShareableValuesHashSet();
Iterator<IValue> keysIterator = keysIterator();
while(keysIterator.hasNext()){
keysSet.add(keysIterator.next());
}
return keysSet;
}
/**
* Constructs a collection containing all values from this map.
*/
public Collection<V> values(){
ShareableHashSet<V> valuesSet = new ShareableHashSet<>();
Iterator<V> valuesIterator = valuesIterator();
while(valuesIterator.hasNext()){
valuesSet.add(valuesIterator.next());
}
return valuesSet;
}
/**
* Prints the internal representation of this map to a string.
*
* @see java.lang.Object#toString()
*/
public String toString(){
StringBuilder buffer = new StringBuilder();
buffer.append('{');
for(int i = 0; i < data.length; i++){
buffer.append('[');
Entry<V> e = data[i];
if(e != null){
buffer.append(e);
e = e.next;
while(e != null){
buffer.append(',');
buffer.append(e);
e = e.next;
}
}
buffer.append(']');
}
buffer.append('}');
return buffer.toString();
}
/**
* Returns the current hash code of this map.
*
* @return The current hash code of this map.
*
* @see java.lang.Object#hashCode()
*/
public int hashCode(){
int hash = 0;
Iterator<IValue> keysIterator = keysIterator();
while(keysIterator.hasNext()){
hash ^= keysIterator.next().hashCode();
}
return hash;
}
/**
* Check whether or not the current content of this set is equal to that of the given object / map.
*
* @return True if the content of this set is equal to the given object / map.
*
* @see java.lang.Object#equals(Object)
*/
public boolean equals(Object o){
if(o == null) return false;
if(o.getClass() == getClass()){
ValueIndexedHashMap<V> other = (ValueIndexedHashMap<V>) o;
if(other.size() != size()) return false;
if(isEmpty()) return true; // No need to check if the maps are empty.
Iterator<Map.Entry<IValue, V>> otherIterator = other.entryIterator();
while(otherIterator.hasNext()){
Map.Entry<IValue, V> entry = otherIterator.next();
V otherValue = entry.getValue();
V thisValue = get(entry.getKey());
if(otherValue != thisValue && thisValue != null && !thisValue.equals(entry.getValue())) return false;
}
return true;
}
return false;
}
/**
* Entry, used for containing key-value pairs and constructing buckets.
*
* @author Arnold Lankamp
*
* @param <K>
* The key type
* @param <V>
* The value type
*/
private static class Entry<V> implements Map.Entry<IValue, V>{
public final int hash;
public final IValue key;
public final V value;
public final Entry<V> next;
/**
* Constructor
*
* @param hash
* The hash code of the key
* @param key
* The key
* @param value
* The value
* @param next
* A reference to the next entry in the bucket (if any).
*/
public Entry(int hash, IValue key, V value, Entry<V> next){
super();
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
/**
* Returns a reference to the key.
*
* @return A reference to the key.
*/
public IValue getKey(){
return key;
}
/**
* Returns a reference to the value.
*
* @return A reference to the value.
*/
public V getValue(){
return value;
}
/**
* Unsupported operation.
*
* @param value
* The value which we will not set.
* @return Null.
* @throws java.lang.UnsupportedOperationException
*
* @see java.util.Map.Entry#setValue(Object)
*/
public V setValue(V value){
throw new UnsupportedOperationException("The setting of values is not supported by this map implementation.");
}
/**
* Prints the internal representation of this entry to a string.
*
* @see java.lang.Object#toString()
*/
public String toString(){
StringBuilder buffer = new StringBuilder();
buffer.append('<');
buffer.append(key);
buffer.append(':');
buffer.append(value);
buffer.append('>');
return buffer.toString();
}
}
/**
* Iterator for entries.
*
* @author Arnold Lankamp
*
* @param <K>
* The key type.
* @param <V>
* The value type.
*/
private static class EntryIterator<V> implements Iterator<Map.Entry<IValue, V>>{
private final Entry<V>[] data;
private Entry<V> current;
private int index;
/**
* Constructor.
*
* @param entries
* The entries to iterator over.
*/
public EntryIterator(Entry<V>[] entries){
super();
data = entries;
index = data.length - 1;
current = new Entry<>(0, null, null, data[index]);
locateNext();
}
/**
* Locates the next entry in the map.
*/
private void locateNext(){
Entry<V> next = current.next;
if(next != null){
current = next;
return;
}
for(int i = index - 1; i >= 0 ; i--){
Entry<V> entry = data[i];
if(entry != null){
current = entry;
index = i;
return;
}
}
current = null;
index = 0;
}
/**
* Check if there are more elements in this iteration.
*
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext(){
return (current != null);
}
/**
* Returns the next element in this iteration.
*
* @return The next element in this iteration.
* @throws NoSuchElementException
* Thrown if there are no more elements in this iteration when calling this
* method.
*
* @see java.util.Iterator#next()
*/
public Entry<V> next(){
if(!hasNext()) throw new UnsupportedOperationException("There are no more elements in this iterator.");
Entry<V> entry = current;
locateNext();
return entry;
}
/**
* Removal is not supported by this iterator.
*
* @throws java.lang.UnsupportedOperationException
*
* @see java.util.Iterator#remove()
*/
public void remove(){
throw new UnsupportedOperationException("This iterator doesn't support removal.");
}
}
/**
* Iterator for keys.
*
* @author Arnold Lankamp
*
* @param <K>
* The key type.
* @param <V>
* The value type.
*/
private static class KeysIterator<V> implements Iterator<IValue>{
private final EntryIterator<V> entryIterator;
/**
* Constructor.
*
* @param entries
* The entries to iterate over.
*/
public KeysIterator(Entry<V>[] entries){
super();
entryIterator = new EntryIterator<>(entries);
}
/**
* Check if there are more elements in this iteration.
*
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext(){
return entryIterator.hasNext();
}
/**
* Returns the next element in this iteration.
*
* @return The next element in this iteration.
* @throws NoSuchElementException
* Thrown if there are no more elements in this iteration when calling this
* method.
*
* @see java.util.Iterator#next()
*/
public IValue next(){
return entryIterator.next().key;
}
/**
* Removal is not supported by this iterator.
*
* @throws java.lang.UnsupportedOperationException
*
* @see java.util.Iterator#remove()
*/
public void remove(){
throw new UnsupportedOperationException("This iterator doesn't support removal.");
}
}
/**
* Iterator for values.
*
* @author Arnold Lankamp
*
* @param <K>
* The key type.
* @param <V>
* The value type.
*/
private static class ValuesIterator<V> implements Iterator<V>{
private final EntryIterator<V> entryIterator;
/**
* Constructor.
*
* @param entries
* The entries to iterate over.
*/
public ValuesIterator(Entry<V>[] entries){
super();
entryIterator = new EntryIterator<>(entries);
}
/**
* Check if there are more elements in this iteration.
*
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext(){
return entryIterator.hasNext();
}
/**
* Returns the next element in this iteration.
*
* @return The next element in this iteration.
* @throws NoSuchElementException
* Thrown if there are no more elements in this iteration when calling this
* method.
*
* @see java.util.Iterator#next()
*/
public V next(){
return entryIterator.next().value;
}
/**
* Removal is not supported by this iterator.
*
* @throws java.lang.UnsupportedOperationException
*
* @see java.util.Iterator#remove()
*/
public void remove(){
throw new UnsupportedOperationException("This iterator doesn't support removal.");
}
}
}