org.pcollections.HashPMap Maven / Gradle / Ivy
/*
* Copyright (c) 2008 Harold Cooper. All rights reserved.
* Licensed under the MIT License.
* See LICENSE file in the project root for full license information.
*/
package org.pcollections;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* A persistent map from non-null keys to non-null values.
*
* This map uses a given integer map to map hashcodes to lists of elements with the same
* hashcode. Thus if all elements have the same hashcode, performance is reduced to that of an
* association list.
*
*
This implementation is thread-safe (assuming Java's AbstractMap and AbstractSet are
* thread-safe), although its iterators may not be.
*
* @author harold
* @param
* @param
*/
public final class HashPMap extends AbstractMap implements PMap, Serializable {
private static final long serialVersionUID = 1L;
//// STATIC FACTORY METHODS ////
/**
* @param
* @param
* @param intMap
* @return a map backed by an empty version of intMap, i.e. backed by
* intMap.minusAll(intMap.keySet())
*/
public static HashPMap empty(final PMap>> intMap) {
return new HashPMap(intMap.minusAll(intMap.keySet()), 0);
}
//// PRIVATE CONSTRUCTORS ////
private final PMap>> intMap;
private final int size;
// not externally instantiable (or subclassable):
private HashPMap(final PMap>> intMap, final int size) {
this.intMap = intMap;
this.size = size;
}
//// REQUIRED METHODS FROM AbstractMap ////
// this cache variable is thread-safe since assignment in Java is atomic:
private transient Set> entrySet = null;
@Override
public Set> entrySet() {
if (entrySet == null)
entrySet =
new AbstractSet>() {
// REQUIRED METHODS OF AbstractSet //
@Override
public int size() {
return size;
}
@Override
public Iterator> iterator() {
return new SequenceIterator>(intMap.values().iterator());
}
// OVERRIDDEN METHODS OF AbstractSet //
@Override
public boolean contains(final Object e) {
if (!(e instanceof Entry)) return false;
V value = get(((Entry, ?>) e).getKey());
return value != null && value.equals(((Entry, ?>) e).getValue());
}
};
return entrySet;
}
//// OVERRIDDEN METHODS FROM AbstractMap ////
@Override
public int size() {
return size;
}
@Override
public boolean containsKey(final Object key) {
return keyIndexIn(getEntries(key.hashCode()), key) != -1;
}
@Override
public V get(final Object key) {
PSequence> entries = getEntries(key.hashCode());
for (Entry entry : entries) if (entry.getKey().equals(key)) return entry.getValue();
return null;
}
//// IMPLEMENTED METHODS OF PMap////
public HashPMap plusAll(final Map extends K, ? extends V> map) {
HashPMap result = this;
for (Entry extends K, ? extends V> entry : map.entrySet())
result = result.plus(entry.getKey(), entry.getValue());
return result;
}
public HashPMap minusAll(final Collection> keys) {
HashPMap result = this;
for (Object key : keys) result = result.minus(key);
return result;
}
public HashPMap plus(final K key, final V value) {
PSequence> entries = getEntries(key.hashCode());
int size0 = entries.size(), i = keyIndexIn(entries, key);
if (i != -1) entries = entries.minus(i);
entries = entries.plus(new SimpleImmutableEntry(key, value));
return new HashPMap(intMap.plus(key.hashCode(), entries), size - size0 + entries.size());
}
public HashPMap minus(final Object key) {
PSequence> entries = getEntries(key.hashCode());
int i = keyIndexIn(entries, key);
if (i == -1) // key not in this
return this;
entries = entries.minus(i);
if (entries.size() == 0) // get rid of the entire hash entry
return new HashPMap(intMap.minus(key.hashCode()), size - 1);
// otherwise replace hash entry with new smaller one:
return new HashPMap(intMap.plus(key.hashCode(), entries), size - 1);
}
//// PRIVATE UTILITIES ////
private PSequence> getEntries(final int hash) {
PSequence> entries = intMap.get(hash);
if (entries == null) return ConsPStack.empty();
return entries;
}
//// PRIVATE STATIC UTILITIES ////
private static int keyIndexIn(final PSequence> entries, final Object key) {
int i = 0;
for (Entry entry : entries) {
if (entry.getKey().equals(key)) return i;
i++;
}
return -1;
}
static class SequenceIterator implements Iterator {
private final Iterator> i;
private PSequence seq = ConsPStack.empty();
SequenceIterator(Iterator> i) {
this.i = i;
}
public boolean hasNext() {
return seq.size() > 0 || i.hasNext();
}
public E next() {
if (seq.size() == 0) seq = i.next();
final E result = seq.get(0);
seq = seq.subList(1, seq.size());
return result;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}