Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.pure4j.collections.PersistentHashMap Maven / Gradle / Ivy
/**
* Copyright (c) Rich Hickey. All rights reserved.
* The use and distribution terms for this software are covered by the
* Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
* which can be found in the file epl-v10.html at the root of this distribution.
* By using this software in any fashion, you are agreeing to be bound by
* the terms of this license.
* You must not remove this notice, or any other, from this software.
**/
package org.pure4j.collections;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.pure4j.Pure4J;
import org.pure4j.annotations.immutable.IgnoreImmutableTypeCheck;
import org.pure4j.annotations.mutable.MutableUnshared;
import org.pure4j.annotations.pure.Enforcement;
import org.pure4j.annotations.pure.Pure;
import org.pure4j.annotations.pure.PureParameters;
/*
A persistent rendition of Phil Bagwell's Hash Array Mapped Trie
Uses path copying for persistence
HashCollision leaves vs. extended hashing
Node polymorphism vs. conditionals
No sub-tree pools or root-resizing
Any errors are my own
*/
public class PersistentHashMap extends APersistentMap implements IMapIterable {
private static final long serialVersionUID = -5413707354958055094L;
final int count;
@IgnoreImmutableTypeCheck
final INode root;
final boolean hasNull;
@IgnoreImmutableTypeCheck
final V nullValue;
final private static PersistentHashMap EMPTY = new PersistentHashMap(0,
null, false, null);
@IgnoreImmutableTypeCheck
final private static Object NOT_FOUND = new Object();
@PureParameters(Enforcement.NOT_PURE)
public PersistentHashMap(Map other) {
this(createTemporary(other));
}
public PersistentHashMap() {
this(0, null, false, null);
}
@Pure
@PureParameters(Enforcement.NOT_PURE)
private static TemporaryHashMap createTemporary(Map other) {
TemporaryHashMap ret = new TemporaryHashMap();
for (Entry o : other.entrySet()) {
ret = (TemporaryHashMap) ret.assoc(o.getKey(), o.getValue());
}
return ret;
}
@Pure
@PureParameters(Enforcement.NOT_PURE)
private static TemporaryHashMap createTemporary(ISeq> other) {
TemporaryHashMap ret = new TemporaryHashMap();
for (Entry o : other) {
ret = (TemporaryHashMap) ret.assoc(o.getKey(), o.getValue());
}
return ret;
}
private PersistentHashMap(TemporaryHashMap in) {
this(in.count, in.root, in.hasNull, in.nullValue);
}
@SafeVarargs
static public IPersistentMap create(K... pairs) {
ITransientMap ret = new TemporaryHashMap();
if (pairs.length % 2 != 0) {
throw new IllegalArgumentException("Argument must supply key/value pairs");
}
for (int i = 0; i < pairs.length; i=i+2) {
ret.put((K) pairs[i], (K) pairs[i+1]);
}
return ret.persistent();
}
@Pure
@SuppressWarnings("unchecked")
public static PersistentHashMap emptyMap() {
return (PersistentHashMap) EMPTY;
}
private PersistentHashMap(int count, INode root, boolean hasNull, V nullValue) {
this.count = count;
this.root = root;
this.hasNull = hasNull;
this.nullValue = nullValue;
}
public PersistentHashMap(ISeq> items) {
this(createTemporary(items));
}
@Pure
static int hash(Object k) {
Pure4J.immutable(k);
return Pure4J.hashCode(k); // should be a less expensive way to do this
}
public boolean containsKey(Object key) {
Pure4J.immutable(key);
if (key == null)
return hasNull;
return (root != null) ? root.find(0, hash(key), key, NOT_FOUND) != NOT_FOUND
: false;
}
@SuppressWarnings("unchecked")
public IMapEntry entryAt(Object key) {
Pure4J.immutable(key);
if (key == null)
return hasNull ? new MapEntry(null, nullValue) : null;
return (root != null) ? root.find(0, hash(key), key) : null;
}
@Pure(Enforcement.FORCE)
/*
* Not pure, specifically because of Box.
* It would be fairly easy to re-write Box so that it has getters and setters, and
* assures it's purity. However, it's part of the implementation, so I won't do this now.
*/
public PersistentHashMap assoc(K key, V val) {
Pure4J.immutable(key, val);
if (key == null) {
if (hasNull && val == nullValue)
return this;
return new PersistentHashMap(hasNull ? count : count + 1,
root, true, val);
}
Box addedLeaf = new Box(null);
INode newroot = (root == null ? BitmapIndexedNode.EMPTY : root).assoc(
0, hash(key), key, val, addedLeaf);
if (newroot == root)
return this;
return new PersistentHashMap(addedLeaf.val == null ? count
: count + 1, newroot, hasNull, nullValue);
}
@SuppressWarnings("unchecked")
public V get(Object key, V notFound) {
Pure4J.immutable(key, notFound);
if (key == null)
return hasNull ? nullValue : notFound;
return (V) (root != null ? root.find(0, hash(key), key, notFound) : notFound);
}
public V get(Object key) {
Pure4J.immutable(key);
return get(key, null);
}
public PersistentHashMap assocEx(K key, V val) {
Pure4J.immutable(key, val);
if (containsKey(key))
throw Util.runtimeException("Key already present");
return assoc(key, val);
}
public IPersistentMap without(Object key) {
Pure4J.immutable(key);
if (key == null)
return hasNull ? new PersistentHashMap(count - 1, root,
false, null) : this;
if (root == null)
return this;
INode newroot = root.without(0, hash(key), key);
if (newroot == root)
return this;
return new PersistentHashMap(count - 1, newroot, hasNull,
nullValue);
}
@IgnoreImmutableTypeCheck
static final IPureIterator EMPTY_ITER = new IPureIterator() {
public boolean hasNext() {
return false;
}
public Object next() {
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
@SuppressWarnings("unchecked")
public static IPureIterator emptyIter() {
return (IPureIterator) EMPTY_ITER;
}
@SuppressWarnings("unchecked")
public Iterator> iterator() {
final Iterator> rootIter = (root == null) ? EMPTY_ITER : root
.iterator();
if (hasNull) {
return new IPureIterator>() {
private boolean seen = false;
public boolean hasNext() {
if (!seen)
return true;
else
return rootIter.hasNext();
}
public Entry next() {
if (!seen) {
seen = true;
return (Entry) nullValue;
} else
return (Entry) rootIter.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
} else
return (Iterator>) rootIter;
}
public IPureIterator keyIterator() {
return KeySeq.create(seq()).iterator();
}
public IPureIterator valIterator() {
return ValSeq.create(seq()).iterator();
}
public int count() {
return count;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public ISeq> seq() {
ISeq> s = root != null ? root.nodeSeq() : null;
MapEntry first = new MapEntry(null, nullValue);
return hasNull ? new Cons(first, s) : s;
}
public PersistentHashMap empty() {
return emptyMap();
}
static int mask(int hash, int shift) {
// return ((hash << shift) >>> 27);// & 0x01f;
return (hash >>> shift) & 0x01f;
}
@Pure(Enforcement.NOT_PURE)
public TemporaryHashMap asTransient() {
return new TemporaryHashMap(this);
}
private static final class TemporaryHashMap extends ATransientMap {
final AtomicReference edit;
volatile INode root;
volatile int count;
volatile boolean hasNull;
volatile V nullValue;
final Box leafFlag = new Box(null);
TemporaryHashMap(PersistentHashMap m) {
this(new AtomicReference(Thread.currentThread()), m.root,
m.count, m.hasNull, m.nullValue);
}
TemporaryHashMap(AtomicReference edit, INode root, int count,
boolean hasNull, V nullValue) {
this.edit = edit;
this.root = root;
this.count = count;
this.hasNull = hasNull;
this.nullValue = nullValue;
}
TemporaryHashMap() {
this(new AtomicReference(Thread.currentThread()), null, 0, false, null);
}
TemporaryHashMap doAssoc(K key, V val) {
if (key == null) {
if (this.nullValue != val)
this.nullValue = val;
if (!hasNull) {
this.count++;
this.hasNull = true;
}
return this;
}
// Box leafFlag = new Box(null);
leafFlag.val = null;
INode n = (root == null ? BitmapIndexedNode.EMPTY : root).assoc(
edit, 0, hash(key), key, val, leafFlag);
if (n != this.root)
this.root = n;
if (leafFlag.val != null)
this.count++;
return this;
}
TemporaryHashMap doWithout(Object key) {
if (key == null) {
if (!hasNull)
return this;
hasNull = false;
nullValue = null;
this.count--;
return this;
}
if (root == null)
return this;
// Box leafFlag = new Box(null);
leafFlag.val = null;
INode n = root.without(edit, 0, hash(key), key, leafFlag);
if (n != root)
this.root = n;
if (leafFlag.val != null)
this.count--;
return this;
}
PersistentHashMap doPersistent() {
edit.set(null);
return new PersistentHashMap(count, root, hasNull, nullValue);
}
@SuppressWarnings("unchecked")
V doValAt(Object key, V notFound) {
if (key == null)
if (hasNull)
return nullValue;
else
return notFound;
if (root == null)
return notFound;
return (V) root.find(0, hash(key), key, notFound);
}
int doCount() {
return count;
}
void ensureEditable() {
if (edit.get() == null)
throw new IllegalAccessError(
"Transient used after persistent! call");
}
@Override
public boolean containsKey(Object key) {
Pure4J.immutable(key);
if (key == null)
return hasNull;
return (root != null) ? root.find(0, hash(key), key, NOT_FOUND) != NOT_FOUND
: false;
}
/**
* Currently very expensive
*/
@Override
public boolean containsValue(Object value) {
return values().contains(value);
}
@SuppressWarnings("unchecked")
@Override
public void clear() {
this.root = EMPTY.root;
this.count = 0;
this.hasNull = EMPTY.hasNull;
this.nullValue = (V) EMPTY.nullValue;
}
@Override
public Set keySet() {
return persistent().keySet();
}
@Override
public Collection values() {
return persistent().values();
}
@Override
public Set> entrySet() {
return persistent().entrySet();
}
}
@MutableUnshared
@SuppressWarnings({"rawtypes"})
static interface INode extends Serializable {
INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf);
INode without(int shift, int hash, Object key);
IMapEntry find(int shift, int hash, Object key);
Object find(int shift, int hash, Object key, Object notFound);
ISeq nodeSeq();
INode assoc(AtomicReference edit, int shift, int hash,
Object key, Object val, Box addedLeaf);
INode without(AtomicReference edit, int shift, int hash,
Object key, Box removedLeaf);
// returns the result of (f [k v]) for each iterated element
Iterator iterator();
}
@SuppressWarnings({"rawtypes", "unchecked"})
final static class ArrayNode implements INode {
int count;
final INode[] array;
final AtomicReference edit;
ArrayNode(AtomicReference edit, int count, INode[] array) {
this.array = array;
this.edit = edit;
this.count = count;
}
public INode assoc(int shift, int hash, Object key, Object val,
Box addedLeaf) {
int idx = mask(hash, shift);
INode node = array[idx];
if (node == null)
return new ArrayNode(null, count + 1, cloneAndSet(array, idx,
BitmapIndexedNode.EMPTY.assoc(shift + 5, hash, key,
val, addedLeaf)));
INode n = node.assoc(shift + 5, hash, key, val, addedLeaf);
if (n == node)
return this;
return new ArrayNode(null, count, cloneAndSet(array, idx, n));
}
public INode without(int shift, int hash, Object key) {
int idx = mask(hash, shift);
INode node = array[idx];
if (node == null)
return this;
INode n = node.without(shift + 5, hash, key);
if (n == node)
return this;
if (n == null) {
if (count <= 8) // shrink
return pack(null, idx);
return new ArrayNode(null, count - 1,
cloneAndSet(array, idx, n));
} else
return new ArrayNode(null, count, cloneAndSet(array, idx, n));
}
public IMapEntry find(int shift, int hash, Object key) {
int idx = mask(hash, shift);
INode node = array[idx];
if (node == null)
return null;
return node.find(shift + 5, hash, key);
}
public Object find(int shift, int hash, Object key, Object notFound) {
int idx = mask(hash, shift);
INode node = array[idx];
if (node == null)
return notFound;
return node.find(shift + 5, hash, key, notFound);
}
public ISeq nodeSeq() {
return Seq.create(array);
}
public Iterator iterator() {
return new Iter(array);
}
private ArrayNode ensureEditable(AtomicReference edit) {
if (this.edit == edit)
return this;
return new ArrayNode(edit, count, this.array.clone());
}
private ArrayNode editAndSet(AtomicReference edit, int i,
INode n) {
ArrayNode editable = ensureEditable(edit);
editable.array[i] = n;
return editable;
}
private INode pack(AtomicReference edit, int idx) {
Object[] newArray = new Object[2 * (count - 1)];
int j = 1;
int bitmap = 0;
for (int i = 0; i < idx; i++)
if (array[i] != null) {
newArray[j] = array[i];
bitmap |= 1 << i;
j += 2;
}
for (int i = idx + 1; i < array.length; i++)
if (array[i] != null) {
newArray[j] = array[i];
bitmap |= 1 << i;
j += 2;
}
return new BitmapIndexedNode(edit, bitmap, newArray);
}
public INode assoc(AtomicReference edit, int shift, int hash,
Object key, Object val, Box addedLeaf) {
int idx = mask(hash, shift);
INode node = array[idx];
if (node == null) {
ArrayNode editable = editAndSet(edit, idx,
BitmapIndexedNode.EMPTY.assoc(edit, shift + 5, hash,
key, val, addedLeaf));
editable.count++;
return editable;
}
INode n = node.assoc(edit, shift + 5, hash, key, val, addedLeaf);
if (n == node)
return this;
return editAndSet(edit, idx, n);
}
public INode without(AtomicReference edit, int shift, int hash,
Object key, Box removedLeaf) {
int idx = mask(hash, shift);
INode node = array[idx];
if (node == null)
return this;
INode n = node.without(edit, shift + 5, hash, key, removedLeaf);
if (n == node)
return this;
if (n == null) {
if (count <= 8) // shrink
return pack(edit, idx);
ArrayNode editable = editAndSet(edit, idx, n);
editable.count--;
return editable;
}
return editAndSet(edit, idx, n);
}
static class Seq extends ASeq {
final INode[] nodes;
final int i;
final ISeq s;
static ISeq create(INode[] nodes) {
return create(nodes, 0, null);
}
private static ISeq create(INode[] nodes,
int i, ISeq s) {
if (s != null)
return new Seq(nodes, i, s);
for (int j = i; j < nodes.length; j++)
if (nodes[j] != null) {
ISeq ns = nodes[j].nodeSeq();
if (ns != null)
return new Seq(nodes, j + 1, ns);
}
return null;
}
private Seq(INode[] nodes, int i, ISeq s) {
this.nodes = nodes;
this.i = i;
this.s = s;
}
public Object first() {
return s.first();
}
public ISeq next() {
return create(nodes, i, s.next());
}
}
static class Iter implements Iterator {
private final INode[] array;
private int i = 0;
private Iterator nestedIter;
private Iter(INode[] array) {
this.array = array;
}
public boolean hasNext() {
while (true) {
if (nestedIter != null)
if (nestedIter.hasNext())
return true;
else
nestedIter = null;
if (i < array.length) {
INode node = array[i++];
if (node != null)
nestedIter = node.iterator();
} else
return false;
}
}
public Object next() {
if (hasNext())
return nestedIter.next();
else
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
final static class BitmapIndexedNode implements INode {
static final BitmapIndexedNode EMPTY = new BitmapIndexedNode(null, 0,
new Object[0]);
int bitmap;
Object[] array;
final AtomicReference edit;
final int index(int bit) {
return Integer.bitCount(bitmap & (bit - 1));
}
BitmapIndexedNode(AtomicReference edit, int bitmap,
Object[] array) {
this.bitmap = bitmap;
this.array = array;
this.edit = edit;
}
public INode assoc(int shift, int hash, Object key, Object val,
Box addedLeaf) {
int bit = bitpos(hash, shift);
int idx = index(bit);
if ((bitmap & bit) != 0) {
Object keyOrNull = array[2 * idx];
Object valOrNode = array[2 * idx + 1];
if (keyOrNull == null) {
INode n = ((INode) valOrNode).assoc(shift + 5, hash, key,
val, addedLeaf);
if (n == valOrNode)
return this;
return new BitmapIndexedNode(null, bitmap, cloneAndSet(
array, 2 * idx + 1, n));
}
if (Util.equals(key, keyOrNull)) {
if (val == valOrNode)
return this;
return new BitmapIndexedNode(null, bitmap, cloneAndSet(
array, 2 * idx + 1, val));
}
addedLeaf.val = addedLeaf;
return new BitmapIndexedNode(null, bitmap, cloneAndSet(
array,
2 * idx,
null,
2 * idx + 1,
createNode(shift + 5, keyOrNull, valOrNode, hash, key,
val)));
} else {
int n = Integer.bitCount(bitmap);
if (n >= 16) {
INode[] nodes = new INode[32];
int jdx = mask(hash, shift);
nodes[jdx] = EMPTY.assoc(shift + 5, hash, key, val,
addedLeaf);
int j = 0;
for (int i = 0; i < 32; i++)
if (((bitmap >>> i) & 1) != 0) {
if (array[j] == null)
nodes[i] = (INode) array[j + 1];
else
nodes[i] = EMPTY.assoc(shift + 5,
hash(array[j]), array[j], array[j + 1],
addedLeaf);
j += 2;
}
return new ArrayNode(null, n + 1, nodes);
} else {
Object[] newArray = new Object[2 * (n + 1)];
System.arraycopy(array, 0, newArray, 0, 2 * idx);
newArray[2 * idx] = key;
addedLeaf.val = addedLeaf;
newArray[2 * idx + 1] = val;
System.arraycopy(array, 2 * idx, newArray, 2 * (idx + 1),
2 * (n - idx));
return new BitmapIndexedNode(null, bitmap | bit, newArray);
}
}
}
public INode without(int shift, int hash, Object key) {
int bit = bitpos(hash, shift);
if ((bitmap & bit) == 0)
return this;
int idx = index(bit);
Object keyOrNull = array[2 * idx];
Object valOrNode = array[2 * idx + 1];
if (keyOrNull == null) {
INode n = ((INode) valOrNode).without(shift + 5, hash, key);
if (n == valOrNode)
return this;
if (n != null)
return new BitmapIndexedNode(null, bitmap, cloneAndSet(
array, 2 * idx + 1, n));
if (bitmap == bit)
return null;
return new BitmapIndexedNode(null, bitmap ^ bit, removePair(
array, idx));
}
if (Util.equals(key, keyOrNull))
// TODO: collapse
return new BitmapIndexedNode(null, bitmap ^ bit, removePair(
array, idx));
return this;
}
public IMapEntry find(int shift, int hash, Object key) {
int bit = bitpos(hash, shift);
if ((bitmap & bit) == 0)
return null;
int idx = index(bit);
Object keyOrNull = array[2 * idx];
Object valOrNode = array[2 * idx + 1];
if (keyOrNull == null)
return ((INode) valOrNode).find(shift + 5, hash, key);
if (Util.equals(key, keyOrNull))
return new MapEntry(keyOrNull, valOrNode);
return null;
}
public Object find(int shift, int hash, Object key, Object notFound) {
int bit = bitpos(hash, shift);
if ((bitmap & bit) == 0)
return notFound;
int idx = index(bit);
Object keyOrNull = array[2 * idx];
Object valOrNode = array[2 * idx + 1];
if (keyOrNull == null)
return ((INode) valOrNode).find(shift + 5, hash, key, notFound);
if (Util.equals(key, keyOrNull))
return valOrNode;
return notFound;
}
public ISeq nodeSeq() {
return NodeSeq.create(array);
}
public Iterator iterator() {
return new NodeIter(array);
}
private BitmapIndexedNode ensureEditable(AtomicReference edit) {
if (this.edit == edit)
return this;
int n = Integer.bitCount(bitmap);
Object[] newArray = new Object[n >= 0 ? 2 * (n + 1) : 4]; // make
// room
// for
// next
// assoc
System.arraycopy(array, 0, newArray, 0, 2 * n);
return new BitmapIndexedNode(edit, bitmap, newArray);
}
private BitmapIndexedNode editAndSet(AtomicReference edit,
int i, Object a) {
BitmapIndexedNode editable = ensureEditable(edit);
editable.array[i] = a;
return editable;
}
private BitmapIndexedNode editAndSet(AtomicReference edit,
int i, Object a, int j, Object b) {
BitmapIndexedNode editable = ensureEditable(edit);
editable.array[i] = a;
editable.array[j] = b;
return editable;
}
private BitmapIndexedNode editAndRemovePair(
AtomicReference edit, int bit, int i) {
if (bitmap == bit)
return null;
BitmapIndexedNode editable = ensureEditable(edit);
editable.bitmap ^= bit;
System.arraycopy(editable.array, 2 * (i + 1), editable.array,
2 * i, editable.array.length - 2 * (i + 1));
editable.array[editable.array.length - 2] = null;
editable.array[editable.array.length - 1] = null;
return editable;
}
public INode assoc(AtomicReference edit, int shift, int hash,
Object key, Object val, Box addedLeaf) {
int bit = bitpos(hash, shift);
int idx = index(bit);
if ((bitmap & bit) != 0) {
Object keyOrNull = array[2 * idx];
Object valOrNode = array[2 * idx + 1];
if (keyOrNull == null) {
INode n = ((INode) valOrNode).assoc(edit, shift + 5, hash,
key, val, addedLeaf);
if (n == valOrNode)
return this;
return editAndSet(edit, 2 * idx + 1, n);
}
if (Util.equals(key, keyOrNull)) {
if (val == valOrNode)
return this;
return editAndSet(edit, 2 * idx + 1, val);
}
addedLeaf.val = addedLeaf;
return editAndSet(
edit,
2 * idx,
null,
2 * idx + 1,
createNode(edit, shift + 5, keyOrNull, valOrNode, hash,
key, val));
} else {
int n = Integer.bitCount(bitmap);
if (n * 2 < array.length) {
addedLeaf.val = addedLeaf;
BitmapIndexedNode editable = ensureEditable(edit);
System.arraycopy(editable.array, 2 * idx, editable.array,
2 * (idx + 1), 2 * (n - idx));
editable.array[2 * idx] = key;
editable.array[2 * idx + 1] = val;
editable.bitmap |= bit;
return editable;
}
if (n >= 16) {
INode[] nodes = new INode[32];
int jdx = mask(hash, shift);
nodes[jdx] = EMPTY.assoc(edit, shift + 5, hash, key, val,
addedLeaf);
int j = 0;
for (int i = 0; i < 32; i++)
if (((bitmap >>> i) & 1) != 0) {
if (array[j] == null)
nodes[i] = (INode) array[j + 1];
else
nodes[i] = EMPTY.assoc(edit, shift + 5,
hash(array[j]), array[j], array[j + 1],
addedLeaf);
j += 2;
}
return new ArrayNode(edit, n + 1, nodes);
} else {
Object[] newArray = new Object[2 * (n + 4)];
System.arraycopy(array, 0, newArray, 0, 2 * idx);
newArray[2 * idx] = key;
addedLeaf.val = addedLeaf;
newArray[2 * idx + 1] = val;
System.arraycopy(array, 2 * idx, newArray, 2 * (idx + 1),
2 * (n - idx));
BitmapIndexedNode editable = ensureEditable(edit);
editable.array = newArray;
editable.bitmap |= bit;
return editable;
}
}
}
public INode without(AtomicReference edit, int shift, int hash,
Object key, Box removedLeaf) {
int bit = bitpos(hash, shift);
if ((bitmap & bit) == 0)
return this;
int idx = index(bit);
Object keyOrNull = array[2 * idx];
Object valOrNode = array[2 * idx + 1];
if (keyOrNull == null) {
INode n = ((INode) valOrNode).without(edit, shift + 5, hash,
key, removedLeaf);
if (n == valOrNode)
return this;
if (n != null)
return editAndSet(edit, 2 * idx + 1, n);
if (bitmap == bit)
return null;
return editAndRemovePair(edit, bit, idx);
}
if (Util.equals(key, keyOrNull)) {
removedLeaf.val = removedLeaf;
// TODO: collapse
return editAndRemovePair(edit, bit, idx);
}
return this;
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
final static class HashCollisionNode implements INode {
final int hash;
int count;
Object[] array;
final AtomicReference edit;
HashCollisionNode(AtomicReference edit, int hash, int count,
Object... array) {
this.edit = edit;
this.hash = hash;
this.count = count;
this.array = array;
}
public INode assoc(int shift, int hash, Object key, Object val,
Box addedLeaf) {
if (hash == this.hash) {
int idx = findIndex(key);
if (idx != -1) {
if (array[idx + 1] == val)
return this;
return new HashCollisionNode(null, hash, count,
cloneAndSet(array, idx + 1, val));
}
Object[] newArray = new Object[2 * (count + 1)];
System.arraycopy(array, 0, newArray, 0, 2 * count);
newArray[2 * count] = key;
newArray[2 * count + 1] = val;
addedLeaf.val = addedLeaf;
return new HashCollisionNode(edit, hash, count + 1, newArray);
}
// nest it in a bitmap node
return new BitmapIndexedNode(null, bitpos(this.hash, shift),
new Object[] { null, this }).assoc(shift, hash, key, val,
addedLeaf);
}
public INode without(int shift, int hash, Object key) {
int idx = findIndex(key);
if (idx == -1)
return this;
if (count == 1)
return null;
return new HashCollisionNode(null, hash, count - 1, removePair(
array, idx / 2));
}
public IMapEntry find(int shift, int hash, Object key) {
int idx = findIndex(key);
if (idx < 0)
return null;
if (Util.equals(key, array[idx]))
return new MapEntry(array[idx], array[idx + 1]);
return null;
}
public Object find(int shift, int hash, Object key, Object notFound) {
int idx = findIndex(key);
if (idx < 0)
return notFound;
if (Util.equals(key, array[idx]))
return array[idx + 1];
return notFound;
}
public ISeq nodeSeq() {
return NodeSeq.create(array);
}
public Iterator iterator() {
return new NodeIter(array);
}
public int findIndex(Object key) {
for (int i = 0; i < 2 * count; i += 2) {
if (Util.equals(key, array[i]))
return i;
}
return -1;
}
private HashCollisionNode ensureEditable(AtomicReference edit) {
if (this.edit == edit)
return this;
Object[] newArray = new Object[2 * (count + 1)]; // make room for
// next assoc
System.arraycopy(array, 0, newArray, 0, 2 * count);
return new HashCollisionNode(edit, hash, count, newArray);
}
private HashCollisionNode ensureEditable(AtomicReference edit,
int count, Object[] array) {
if (this.edit == edit) {
this.array = array;
this.count = count;
return this;
}
return new HashCollisionNode(edit, hash, count, array);
}
private HashCollisionNode editAndSet(AtomicReference edit,
int i, Object a) {
HashCollisionNode editable = ensureEditable(edit);
editable.array[i] = a;
return editable;
}
private HashCollisionNode editAndSet(AtomicReference edit,
int i, Object a, int j, Object b) {
HashCollisionNode editable = ensureEditable(edit);
editable.array[i] = a;
editable.array[j] = b;
return editable;
}
public INode assoc(AtomicReference edit, int shift, int hash,
Object key, Object val, Box addedLeaf) {
if (hash == this.hash) {
int idx = findIndex(key);
if (idx != -1) {
if (array[idx + 1] == val)
return this;
return editAndSet(edit, idx + 1, val);
}
if (array.length > 2 * count) {
addedLeaf.val = addedLeaf;
HashCollisionNode editable = editAndSet(edit, 2 * count,
key, 2 * count + 1, val);
editable.count++;
return editable;
}
Object[] newArray = new Object[array.length + 2];
System.arraycopy(array, 0, newArray, 0, array.length);
newArray[array.length] = key;
newArray[array.length + 1] = val;
addedLeaf.val = addedLeaf;
return ensureEditable(edit, count + 1, newArray);
}
// nest it in a bitmap node
return new BitmapIndexedNode(edit, bitpos(this.hash, shift),
new Object[] { null, this, null, null }).assoc(edit, shift,
hash, key, val, addedLeaf);
}
public INode without(AtomicReference edit, int shift, int hash,
Object key, Box removedLeaf) {
int idx = findIndex(key);
if (idx == -1)
return this;
removedLeaf.val = removedLeaf;
if (count == 1)
return null;
HashCollisionNode editable = ensureEditable(edit);
editable.array[idx] = editable.array[2 * count - 2];
editable.array[idx + 1] = editable.array[2 * count - 1];
editable.array[2 * count - 2] = editable.array[2 * count - 1] = null;
editable.count--;
return editable;
}
}
private static INode[] cloneAndSet(INode[] array, int i, INode a) {
INode[] clone = array.clone();
clone[i] = a;
return clone;
}
private static Object[] cloneAndSet(Object[] array, int i, Object a) {
Object[] clone = array.clone();
clone[i] = a;
return clone;
}
private static Object[] cloneAndSet(Object[] array, int i, Object a, int j,
Object b) {
Object[] clone = array.clone();
clone[i] = a;
clone[j] = b;
return clone;
}
private static Object[] removePair(Object[] array, int i) {
Object[] newArray = new Object[array.length - 2];
System.arraycopy(array, 0, newArray, 0, 2 * i);
System.arraycopy(array, 2 * (i + 1), newArray, 2 * i, newArray.length
- 2 * i);
return newArray;
}
private static INode createNode(int shift, Object key1, Object val1,
int key2hash, Object key2, Object val2) {
int key1hash = hash(key1);
if (key1hash == key2hash)
return new HashCollisionNode(null, key1hash, 2, new Object[] {
key1, val1, key2, val2 });
Box addedLeaf = new Box(null);
AtomicReference edit = new AtomicReference();
return BitmapIndexedNode.EMPTY.assoc(edit, shift, key1hash, key1, val1,
addedLeaf).assoc(edit, shift, key2hash, key2, val2, addedLeaf);
}
private static INode createNode(AtomicReference edit, int shift,
Object key1, Object val1, int key2hash, Object key2, Object val2) {
int key1hash = hash(key1);
if (key1hash == key2hash)
return new HashCollisionNode(null, key1hash, 2, new Object[] {
key1, val1, key2, val2 });
Box addedLeaf = new Box(null);
return BitmapIndexedNode.EMPTY.assoc(edit, shift, key1hash, key1, val1,
addedLeaf).assoc(edit, shift, key2hash, key2, val2, addedLeaf);
}
private static int bitpos(int hash, int shift) {
return 1 << mask(hash, shift);
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static final class NodeIter implements Iterator {
private static final Object NULL = new Object();
final Object[] array;
private int i = 0;
private Object nextEntry = NULL;
private Iterator nextIter;
NodeIter(Object[] array) {
this.array = array;
}
private boolean advance() {
while (i < array.length) {
Object key = array[i];
Object nodeOrVal = array[i + 1];
i += 2;
if (key != null) {
nextEntry = new MapEntry(key, nodeOrVal);
return true;
} else if (nodeOrVal != null) {
Iterator iter = ((INode) nodeOrVal).iterator();
if (iter != null && iter.hasNext()) {
nextIter = iter;
return true;
}
}
}
return false;
}
public boolean hasNext() {
if (nextEntry != NULL || nextIter != null)
return true;
return advance();
}
public Object next() {
Object ret = nextEntry;
if (ret != NULL) {
nextEntry = NULL;
return ret;
} else if (nextIter != null) {
ret = nextIter.next();
if (!nextIter.hasNext())
nextIter = null;
return ret;
} else if (advance())
return next();
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
static final class NodeSeq extends ASeq {
final Object[] array;
final int i;
final ISeq s;
NodeSeq(Object[] array, int i) {
this(array, i, null);
}
static ISeq create(Object[] array) {
return create(array, 0, null);
}
private static ISeq create(Object[] array, int i, ISeq s) {
if (s != null)
return new NodeSeq(array, i, s);
for (int j = i; j < array.length; j += 2) {
if (array[j] != null)
return new NodeSeq(array, j, null);
INode node = (INode) array[j + 1];
if (node != null) {
ISeq nodeSeq = node.nodeSeq();
if (nodeSeq != null)
return new NodeSeq(array, j + 2, nodeSeq);
}
}
return null;
}
NodeSeq(Object[] array, int i, ISeq s) {
this.array = array;
this.i = i;
this.s = s;
}
public Object first() {
if (s != null)
return s.first();
return new MapEntry(array[i], array[i + 1]);
}
public ISeq next() {
if (s != null)
return create(array, i, s.next());
return create(array, i + 2, null);
}
}
}