net.sf.saxon.ma.trie.ImmutableHashTrieMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.ma.trie;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
// Original author: Michael Froh (published on Github). Released under MPL 2.0
// by Saxonica Limited with permission from the author
public abstract class ImmutableHashTrieMap
implements ImmutableMap, Iterable> {
private static final ImmutableHashTrieMap EMPTY_NODE = new EmptyHashNode();
public static ImmutableHashTrieMap empty() {
return EMPTY_NODE;
}
private static int getBucket(int shift, K key) {
return key.hashCode() >> shift & MASK;
}
public ImmutableHashTrieMap put(K key, V value) {
return put(0, key, value);
}
public ImmutableHashTrieMap remove(K key) {
return remove(0, key);
}
public Option get(K key) {
return get(0, key);
}
/*
* In the methods below, "shift" denotes how far (in bits) through the
* hash code we are currently looking. At each level, we add "bits",
* defined below, to shift.
*/
private static final int BITS = 5;
private static final int FANOUT = 1 << BITS;
private static final int MASK = FANOUT - 1;
abstract ImmutableHashTrieMap put(int shift, K key,
V value);
abstract ImmutableHashTrieMap remove(int shift, K key);
abstract Option get(int shift, K key);
abstract boolean isArrayNode();
/**
* Implementation for an empty map
* @param the key type
* @param the value type
*/
private static class EmptyHashNode
extends ImmutableHashTrieMap {
@Override
ImmutableHashTrieMap put(final int shift, final K key,
final V value) {
return new EntryHashNode(key, value);
}
@Override
ImmutableHashTrieMap remove(final int shift,
final K key) {
return this;
}
@Override
boolean isArrayNode() {
return false;
}
@Override
Option get(final int shift, final K key) {
return Option.none();
}
public Iterator> iterator() {
return Collections.>emptySet().iterator();
}
}
/**
* Implementation for a single-entry map
* @param the key type
* @param the value type
*/
private static class EntryHashNode
extends ImmutableHashTrieMap {
private final K key;
private final V value;
private EntryHashNode(final K key,
final V value) {
this.key = key;
this.value = value;
}
@Override
ImmutableHashTrieMap put(final int shift, final K key,
final V value) {
if (this.key.equals(key)) {
// Overwriting this entry
return new EntryHashNode(key, value);
} else if (this.key.hashCode() == key.hashCode()) {
// This is a collision. Return a new ListHashNode.
return new ListHashNode(new Tuple2(this.key, this.value),
new Tuple2(key, value));
}
// Split this node into an ArrayHashNode with this and the new value
// as entries.
return newArrayHashNode(shift, this.key.hashCode(), this,
key.hashCode(), new EntryHashNode(key, value));
}
@Override
ImmutableHashTrieMap remove(final int shift,
final K key) {
if (this.key.equals(key)) {
return empty();
}
return this;
}
@Override
boolean isArrayNode() {
return false;
}
@Override
Option get(final int shift, final K key) {
if (this.key.equals(key)) {
return Option.option(value);
}
return Option.none();
}
public Iterator> iterator() {
return Collections.singleton(new Tuple2(key, value)).iterator();
}
}
/**
* Implementation for a set of entries where the keys of all entries share the same hash code
* @param the key type
* @param the value type
*/
private static class ListHashNode extends ImmutableHashTrieMap {
private final ImmutableList> entries;
public ListHashNode(Tuple2 entry1,
Tuple2 entry2) {
// These entries must collide
assert entry1._1.hashCode() == entry2._1.hashCode();
entries = ImmutableList.>nil().prepend(entry1)
.prepend(entry2);
}
private ListHashNode(final ImmutableList> entries) {
// Size should be at least 2
assert !entries.isEmpty();
assert !entries.tail().isEmpty();
this.entries = entries;
}
@Override
ImmutableHashTrieMap put(final int shift, final K key,
final V value) {
if (entries.head()._1.hashCode() != key.hashCode()) {
return newArrayHashNode(shift,
entries.head()._1.hashCode(),
this,
key.hashCode(),
new EntryHashNode(
key, value));
}
ImmutableList> newList = ImmutableList.nil();
boolean found = false;
for (Tuple2 entry : entries) {
if (entry._1.equals(key)) {
// Node replacement
newList =
newList.prepend(new Tuple2(key, value));
found = true;
} else {
newList = newList.prepend(entry);
}
}
if (!found) {
// Adding a new entry
newList = newList.prepend(new Tuple2(key, value));
}
return new ListHashNode(newList);
}
@Override
ImmutableHashTrieMap remove(final int shift,
final K key) {
ImmutableList> newList = ImmutableList.nil();
int size = 0;
for (Tuple2 entry : entries) {
if (!entry._1.equals(key)) {
newList = newList.prepend(entry);
size++;
}
}
if (size == 1) {
Tuple2 entry = newList.head();
return new EntryHashNode(entry._1, entry._2);
}
return new ListHashNode(newList);
}
@Override
boolean isArrayNode() {
return false;
}
@Override
Option get(final int shift, final K key) {
for (Tuple2 entry : entries) {
if (entry._1.equals(key)) {
return Option.option(entry._2);
}
}
return Option.none();
}
public Iterator> iterator() {
return new Iterator>() {
private ImmutableList> curList =
ListHashNode.this.entries;
public boolean hasNext() {
return !curList.isEmpty();
}
public Tuple2 next() {
Tuple2 retVal = curList.head();
curList = curList.tail();
return retVal;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Create a new node combining two existing nodes
* @param shift the shift to be applied to the hash code at this level in the tree
* @param hash1 the hash code of the first node
* @param subNode1 the first node
* @param hash2 the hash code of the second node
* @param subNode2 the second node
* @param the key type
* @param the value type
* @return the new node
*/
private static ImmutableHashTrieMap
newArrayHashNode(int shift,
int hash1,
ImmutableHashTrieMap subNode1,
int hash2,
ImmutableHashTrieMap subNode2) {
int curShift = shift;
int h1 = hash1 >> shift & MASK;
int h2 = hash2 >> shift & MASK;
List buckets = new LinkedList();
while (h1 == h2) {
buckets.add(0, h1);
curShift += BITS;
h1 = hash1 >> curShift & MASK;
h2 = hash2 >> curShift & MASK;
}
ImmutableHashTrieMap newNode =
new BranchedArrayHashNode(h1, subNode1, h2, subNode2);
for (Integer bucket : buckets) {
newNode = new SingletonArrayHashNode(bucket, newNode);
}
return newNode;
}
private static abstract class ArrayHashNode
extends ImmutableHashTrieMap {
@Override
boolean isArrayNode() {
return true;
}
}
private static class BranchedArrayHashNode
extends ArrayHashNode {
private final ImmutableHashTrieMap[] subnodes;
private final int size;
public BranchedArrayHashNode(int h1,
ImmutableHashTrieMap subNode1,
int h2,
ImmutableHashTrieMap subNode2) {
assert h1 != h2;
size = 2;
subnodes = new ImmutableHashTrieMap[FANOUT];
for (int i = 0; i < FANOUT; i++) {
if (i == h1) {
subnodes[i] = subNode1;
} else if (i == h2) {
subnodes[i] = subNode2;
} else {
subnodes[i] = EMPTY_NODE;
}
}
}
public BranchedArrayHashNode(int size,
final ImmutableHashTrieMap[] subnodes) {
assert subnodes.length == FANOUT;
this.size = size;
this.subnodes = subnodes;
}
@Override
ImmutableHashTrieMap put(final int shift, final K key,
final V value) {
final int bucket = getBucket(shift, key);
ImmutableHashTrieMap[] newNodes = new ImmutableHashTrieMap[FANOUT];
System.arraycopy(subnodes, 0, newNodes, 0, FANOUT);
final int newSize =
newNodes[bucket] == EMPTY_NODE ? size + 1 : size;
newNodes[bucket] = newNodes[bucket].put(shift + BITS,
key, value);
return new BranchedArrayHashNode(newSize, newNodes);
}
@Override
ImmutableHashTrieMap remove(final int shift,
final K key) {
final int bucket = getBucket(shift, key);
if (subnodes[bucket] == EMPTY_NODE) {
return this;
}
ImmutableHashTrieMap[] newNodes = new ImmutableHashTrieMap[FANOUT];
System.arraycopy(subnodes, 0, newNodes, 0, FANOUT);
newNodes[bucket] = newNodes[bucket].remove(shift + BITS,
key);
final int newSize =
newNodes[bucket] == EMPTY_NODE ? size - 1 : size;
if (newSize == 1) {
int orphanedBucket = -1;
for (int i = 0; i < FANOUT; i++) {
if (newNodes[i] != EMPTY_NODE) {
orphanedBucket = i;
break;
}
}
ImmutableHashTrieMap orphanedEntry =
subnodes[orphanedBucket];
if (orphanedEntry.isArrayNode()) {
return new SingletonArrayHashNode(orphanedBucket,
orphanedEntry);
}
return orphanedEntry;
}
return new BranchedArrayHashNode(newSize, newNodes);
}
@Override
Option get(final int shift, final K key) {
final int bucket = getBucket(shift, key);
return subnodes[bucket].get(shift + BITS, key);
}
public Iterator> iterator() {
return new Iterator>() {
private int bucket = 0;
private Iterator> childIterator =
subnodes[0].iterator();
public boolean hasNext() {
if (childIterator.hasNext()) {
return true;
}
bucket++;
while (bucket < FANOUT) {
childIterator = subnodes[bucket].iterator();
if (childIterator.hasNext()) {
return true;
}
bucket++;
}
return false;
}
public Tuple2 next() {
return childIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
private static class SingletonArrayHashNode extends
ArrayHashNode {
private final int bucket;
private final ImmutableHashTrieMap subnode;
private SingletonArrayHashNode(final int bucket,
final ImmutableHashTrieMap subnode) {
assert subnode instanceof ArrayHashNode;
this.bucket = bucket;
this.subnode = subnode;
}
@Override
ImmutableHashTrieMap put(final int shift, final K key,
final V value) {
final int bucket = getBucket(shift, key);
if (bucket == this.bucket) {
return new SingletonArrayHashNode(bucket,
subnode.put(shift + BITS, key, value));
}
return new BranchedArrayHashNode(this.bucket, subnode,
bucket, new EntryHashNode(key, value));
}
@Override
ImmutableHashTrieMap remove(final int shift, final K key) {
final int bucket = getBucket(shift, key);
if (bucket == this.bucket) {
ImmutableHashTrieMap newNode =
subnode.remove(shift + BITS, key);
if (!(newNode.isArrayNode())) {
return newNode;
}
return new SingletonArrayHashNode(bucket, newNode);
}
return this;
}
@Override
Option get(final int shift, final K key) {
final int bucket = getBucket(shift, key);
if (bucket == this.bucket) {
return subnode.get(shift + BITS, key);
}
return Option.none();
}
public Iterator> iterator() {
return subnode.iterator();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy